From 9946d1814f4af9ef67e5accd9404e0fc69290f6f Mon Sep 17 00:00:00 2001
From: JassJam <0139ne@gmail.com>
Date: Mon, 23 Feb 2026 12:28:50 +0000
Subject: feat: add initial test files
---
tests/projects/csharp/console/xmake.lua | 6 ++++++
1 file changed, 6 insertions(+)
create mode 100644 tests/projects/csharp/console/xmake.lua
(limited to 'tests/projects/csharp/console/xmake.lua')
diff --git a/tests/projects/csharp/console/xmake.lua b/tests/projects/csharp/console/xmake.lua
new file mode 100644
index 000000000..25cd801f0
--- /dev/null
+++ b/tests/projects/csharp/console/xmake.lua
@@ -0,0 +1,6 @@
+add_rules("mode.debug", "mode.release")
+
+target("test")
+ set_kind("binary")
+ add_rules("csharp")
+ add_files("src/Program.cs", "src/test.csproj")
--
cgit v1.3.1
From a92e7c4087443cd91dafe14d2a5aa316a288b7c5 Mon Sep 17 00:00:00 2001
From: JassJam <0139ne@gmail.com>
Date: Sat, 28 Feb 2026 15:37:56 +0000
Subject: feat: generate csproj on demand
---
tests/projects/csharp/console/src/test.csproj | 9 -
tests/projects/csharp/console/xmake.lua | 2 +-
.../console_with_runtime_json/src/app.csproj | 14 -
.../csharp/console_with_runtime_json/xmake.lua | 2 +-
.../csharp/exe_with_library/src/app/app.csproj | 12 -
.../csharp/exe_with_library/src/lib/lib.csproj | 8 -
tests/projects/csharp/exe_with_library/xmake.lua | 4 +-
.../csharp/exe_with_package/src/app.csproj | 12 -
tests/projects/csharp/exe_with_package/xmake.lua | 4 +-
tests/projects/csharp/existing_csproj/.gitignore | 6 +
tests/projects/csharp/existing_csproj/Program.cs | 2 +
.../csharp/existing_csproj/existing_csproj.csproj | 10 +
tests/projects/csharp/existing_csproj/test.lua | 10 +
tests/projects/csharp/existing_csproj/xmake.lua | 6 +
.../multiple_library/src/libalpha/libalpha.csproj | 8 -
.../multiple_library/src/libbeta/libbeta.csproj | 11 -
.../multiple_library/src/sample/sample.csproj | 14 -
tests/projects/csharp/multiple_library/xmake.lua | 6 +-
.../projects/csharp/web_project/src/webapp.csproj | 8 -
tests/projects/csharp/web_project/xmake.lua | 3 +-
xmake/rules/csharp/buildcmd.lua | 36 ++-
xmake/rules/csharp/clean.lua | 2 +-
xmake/rules/csharp/csharp_common.lua | 160 -----------
xmake/rules/csharp/install.lua | 4 +-
xmake/rules/csharp/installcmd.lua | 4 +-
xmake/rules/csharp/load.lua | 21 +-
xmake/rules/csharp/modules/csharp_common.lua | 232 ++++++++++++++++
xmake/rules/csharp/modules/csproj_generator.lua | 301 ++++++++++++++++++++
xmake/rules/csharp/modules/itemgroups.lua | 166 +++++++++++
xmake/rules/csharp/modules/properties.lua | 307 +++++++++++++++++++++
xmake/rules/csharp/xmake.lua | 83 +++++-
31 files changed, 1184 insertions(+), 283 deletions(-)
delete mode 100644 tests/projects/csharp/console/src/test.csproj
delete mode 100644 tests/projects/csharp/console_with_runtime_json/src/app.csproj
delete mode 100644 tests/projects/csharp/exe_with_library/src/app/app.csproj
delete mode 100644 tests/projects/csharp/exe_with_library/src/lib/lib.csproj
delete mode 100644 tests/projects/csharp/exe_with_package/src/app.csproj
create mode 100644 tests/projects/csharp/existing_csproj/.gitignore
create mode 100644 tests/projects/csharp/existing_csproj/Program.cs
create mode 100644 tests/projects/csharp/existing_csproj/existing_csproj.csproj
create mode 100644 tests/projects/csharp/existing_csproj/test.lua
create mode 100644 tests/projects/csharp/existing_csproj/xmake.lua
delete mode 100644 tests/projects/csharp/multiple_library/src/libalpha/libalpha.csproj
delete mode 100644 tests/projects/csharp/multiple_library/src/libbeta/libbeta.csproj
delete mode 100644 tests/projects/csharp/multiple_library/src/sample/sample.csproj
delete mode 100644 tests/projects/csharp/web_project/src/webapp.csproj
delete mode 100644 xmake/rules/csharp/csharp_common.lua
create mode 100644 xmake/rules/csharp/modules/csharp_common.lua
create mode 100644 xmake/rules/csharp/modules/csproj_generator.lua
create mode 100644 xmake/rules/csharp/modules/itemgroups.lua
create mode 100644 xmake/rules/csharp/modules/properties.lua
(limited to 'tests/projects/csharp/console/xmake.lua')
diff --git a/tests/projects/csharp/console/src/test.csproj b/tests/projects/csharp/console/src/test.csproj
deleted file mode 100644
index 9dbab4973..000000000
--- a/tests/projects/csharp/console/src/test.csproj
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- Exe
- net8.0
- enable
- enable
-
-
-
diff --git a/tests/projects/csharp/console/xmake.lua b/tests/projects/csharp/console/xmake.lua
index 25cd801f0..3288c96a8 100644
--- a/tests/projects/csharp/console/xmake.lua
+++ b/tests/projects/csharp/console/xmake.lua
@@ -3,4 +3,4 @@ add_rules("mode.debug", "mode.release")
target("test")
set_kind("binary")
add_rules("csharp")
- add_files("src/Program.cs", "src/test.csproj")
+ add_files("src/Program.cs")
diff --git a/tests/projects/csharp/console_with_runtime_json/src/app.csproj b/tests/projects/csharp/console_with_runtime_json/src/app.csproj
deleted file mode 100644
index 4e754bb15..000000000
--- a/tests/projects/csharp/console_with_runtime_json/src/app.csproj
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
- Exe
- net8.0
- enable
- enable
-
-
-
- PreserveNewest
-
-
-
-
diff --git a/tests/projects/csharp/console_with_runtime_json/xmake.lua b/tests/projects/csharp/console_with_runtime_json/xmake.lua
index 8d8539b26..474eda548 100644
--- a/tests/projects/csharp/console_with_runtime_json/xmake.lua
+++ b/tests/projects/csharp/console_with_runtime_json/xmake.lua
@@ -3,4 +3,4 @@ add_rules("mode.debug", "mode.release")
target("app")
set_kind("binary")
add_rules("csharp")
- add_files("src/Program.cs", "src/app.csproj")
+ add_files("src/Program.cs")
diff --git a/tests/projects/csharp/exe_with_library/src/app/app.csproj b/tests/projects/csharp/exe_with_library/src/app/app.csproj
deleted file mode 100644
index e5e49a685..000000000
--- a/tests/projects/csharp/exe_with_library/src/app/app.csproj
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
- Exe
- net8.0
- enable
- enable
-
-
-
-
-
-
diff --git a/tests/projects/csharp/exe_with_library/src/lib/lib.csproj b/tests/projects/csharp/exe_with_library/src/lib/lib.csproj
deleted file mode 100644
index 30c56ecb5..000000000
--- a/tests/projects/csharp/exe_with_library/src/lib/lib.csproj
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- net8.0
- enable
- enable
-
-
-
diff --git a/tests/projects/csharp/exe_with_library/xmake.lua b/tests/projects/csharp/exe_with_library/xmake.lua
index 063e051db..f8549444e 100644
--- a/tests/projects/csharp/exe_with_library/xmake.lua
+++ b/tests/projects/csharp/exe_with_library/xmake.lua
@@ -3,10 +3,10 @@ add_rules("mode.debug", "mode.release")
target("mylib")
set_kind("shared")
add_rules("csharp")
- add_files("src/lib/*.cs", "src/lib/lib.csproj")
+ add_files("src/lib/*.cs")
target("app")
set_kind("binary")
add_rules("csharp")
add_deps("mylib")
- add_files("src/app/*.cs", "src/app/app.csproj")
+ add_files("src/app/*.cs")
diff --git a/tests/projects/csharp/exe_with_package/src/app.csproj b/tests/projects/csharp/exe_with_package/src/app.csproj
deleted file mode 100644
index 0521e2a0c..000000000
--- a/tests/projects/csharp/exe_with_package/src/app.csproj
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
- Exe
- net8.0
- enable
- enable
-
-
-
-
-
-
diff --git a/tests/projects/csharp/exe_with_package/xmake.lua b/tests/projects/csharp/exe_with_package/xmake.lua
index 8d8539b26..6ec474822 100644
--- a/tests/projects/csharp/exe_with_package/xmake.lua
+++ b/tests/projects/csharp/exe_with_package/xmake.lua
@@ -1,6 +1,8 @@
add_rules("mode.debug", "mode.release")
+add_requires("nuget::Humanizer.Core 2.14.1")
target("app")
set_kind("binary")
add_rules("csharp")
- add_files("src/Program.cs", "src/app.csproj")
+ add_files("src/Program.cs")
+ add_packages("nuget::Humanizer.Core")
diff --git a/tests/projects/csharp/existing_csproj/.gitignore b/tests/projects/csharp/existing_csproj/.gitignore
new file mode 100644
index 000000000..6b8d3af38
--- /dev/null
+++ b/tests/projects/csharp/existing_csproj/.gitignore
@@ -0,0 +1,6 @@
+.xmake/
+build/
+bin/
+obj/
+vsxmake*/
+.vs/
diff --git a/tests/projects/csharp/existing_csproj/Program.cs b/tests/projects/csharp/existing_csproj/Program.cs
new file mode 100644
index 000000000..3751555cb
--- /dev/null
+++ b/tests/projects/csharp/existing_csproj/Program.cs
@@ -0,0 +1,2 @@
+// See https://aka.ms/new-console-template for more information
+Console.WriteLine("Hello, World!");
diff --git a/tests/projects/csharp/existing_csproj/existing_csproj.csproj b/tests/projects/csharp/existing_csproj/existing_csproj.csproj
new file mode 100644
index 000000000..ed9781c22
--- /dev/null
+++ b/tests/projects/csharp/existing_csproj/existing_csproj.csproj
@@ -0,0 +1,10 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
diff --git a/tests/projects/csharp/existing_csproj/test.lua b/tests/projects/csharp/existing_csproj/test.lua
new file mode 100644
index 000000000..5ab8adc69
--- /dev/null
+++ b/tests/projects/csharp/existing_csproj/test.lua
@@ -0,0 +1,10 @@
+import("lib.detect.find_tool")
+
+function test_build(t)
+ local dotnet = find_tool("dotnet")
+ if dotnet then
+ t:build()
+ else
+ return t:skip("dotnet not found")
+ end
+end
diff --git a/tests/projects/csharp/existing_csproj/xmake.lua b/tests/projects/csharp/existing_csproj/xmake.lua
new file mode 100644
index 000000000..95e5e45d9
--- /dev/null
+++ b/tests/projects/csharp/existing_csproj/xmake.lua
@@ -0,0 +1,6 @@
+add_rules("mode.debug", "mode.release")
+
+target("test")
+ set_kind("binary")
+ add_rules("csharp")
+ add_files("existing_csproj.csproj")
diff --git a/tests/projects/csharp/multiple_library/src/libalpha/libalpha.csproj b/tests/projects/csharp/multiple_library/src/libalpha/libalpha.csproj
deleted file mode 100644
index 30c56ecb5..000000000
--- a/tests/projects/csharp/multiple_library/src/libalpha/libalpha.csproj
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- net8.0
- enable
- enable
-
-
-
diff --git a/tests/projects/csharp/multiple_library/src/libbeta/libbeta.csproj b/tests/projects/csharp/multiple_library/src/libbeta/libbeta.csproj
deleted file mode 100644
index 15502d321..000000000
--- a/tests/projects/csharp/multiple_library/src/libbeta/libbeta.csproj
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
- net8.0
- enable
- enable
-
-
-
-
-
-
diff --git a/tests/projects/csharp/multiple_library/src/sample/sample.csproj b/tests/projects/csharp/multiple_library/src/sample/sample.csproj
deleted file mode 100644
index 459440d0e..000000000
--- a/tests/projects/csharp/multiple_library/src/sample/sample.csproj
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- Exe
- net8.0
- enable
- enable
-
-
-
-
-
-
-
diff --git a/tests/projects/csharp/multiple_library/xmake.lua b/tests/projects/csharp/multiple_library/xmake.lua
index b4be98c99..db3f65811 100644
--- a/tests/projects/csharp/multiple_library/xmake.lua
+++ b/tests/projects/csharp/multiple_library/xmake.lua
@@ -3,16 +3,16 @@ add_rules("mode.debug", "mode.release")
target("libalpha")
set_kind("shared")
add_rules("csharp")
- add_files("src/libalpha/*.cs", "src/libalpha/libalpha.csproj")
+ add_files("src/libalpha/*.cs")
target("libbeta")
set_kind("shared")
add_rules("csharp")
add_deps("libalpha")
- add_files("src/libbeta/*.cs", "src/libbeta/libbeta.csproj")
+ add_files("src/libbeta/*.cs")
target("sample")
set_kind("binary")
add_rules("csharp")
add_deps("libbeta")
- add_files("src/sample/*.cs", "src/sample/sample.csproj")
+ add_files("src/sample/*.cs")
diff --git a/tests/projects/csharp/web_project/src/webapp.csproj b/tests/projects/csharp/web_project/src/webapp.csproj
deleted file mode 100644
index 4715481ee..000000000
--- a/tests/projects/csharp/web_project/src/webapp.csproj
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- net8.0
- enable
- enable
-
-
-
diff --git a/tests/projects/csharp/web_project/xmake.lua b/tests/projects/csharp/web_project/xmake.lua
index d1e5504c3..56bcad79c 100644
--- a/tests/projects/csharp/web_project/xmake.lua
+++ b/tests/projects/csharp/web_project/xmake.lua
@@ -3,4 +3,5 @@ add_rules("mode.debug", "mode.release")
target("webapp")
set_kind("binary")
add_rules("csharp")
- add_files("src/Program.cs", "src/webapp.csproj")
+ add_values("csharp.sdk", "Microsoft.NET.Sdk.Web")
+ add_files("src/Program.cs")
diff --git a/xmake/rules/csharp/buildcmd.lua b/xmake/rules/csharp/buildcmd.lua
index e3c45ace8..b8f85360a 100644
--- a/xmake/rules/csharp/buildcmd.lua
+++ b/xmake/rules/csharp/buildcmd.lua
@@ -18,10 +18,35 @@
-- @file buildcmd.lua
--
-import("csharp_common")
+import("modules.csharp_common", {rootdir = os.scriptdir(), alias = "csharp_common"})
+
+local function _get_output_mtime(target, targetfile, targetdirabs)
+ local mtime = targetfile and os.mtime(targetfile) or nil
+ if mtime then
+ return mtime
+ end
+
+ if targetdirabs and os.isdir(targetdirabs) then
+ -- dotnet can place outputs under tfm/rid subdirectories depending on
+ -- project properties, so scan recursively for a matching assembly name.
+ local basename = target:basename() or target:name()
+ for _, ext in ipairs({".exe", ".dll", ".so", ".dylib"}) do
+ for _, file in ipairs(os.files(path.join(targetdirabs, "**", basename .. ext))) do
+ local filemtime = os.mtime(file)
+ if filemtime and (not mtime or filemtime > mtime) then
+ mtime = filemtime
+ end
+ end
+ end
+ if not mtime then
+ mtime = os.mtime(targetdirabs)
+ end
+ end
+ return mtime
+end
function main(target, batchcmds, opt)
- local csprojfile = assert(csharp_common.find_csproj(target), "target(%s): missing csharp .csproj file!", target:name())
+ local csprojfile = assert(csharp_common.find_or_generate_csproj(target), "target(%s): missing csharp .csproj file!", target:name())
local dotnet = csharp_common.get_dotnet_program(target)
local configuration = csharp_common.build_mode_to_configuration()
local verbosity = csharp_common.get_dotnet_verbosity()
@@ -54,9 +79,10 @@ function main(target, batchcmds, opt)
local targetfile = target:targetfile()
if targetfile then
- local sourcefiles = target:sourcefiles()
- batchcmds:add_depfiles(sourcefiles)
- batchcmds:set_depmtime(os.mtime(targetfile))
+ local depfiles = table.wrap(target:sourcefiles())
+ table.insert(depfiles, csprojfile)
+ batchcmds:add_depfiles(table.unique(depfiles))
+ batchcmds:set_depmtime(_get_output_mtime(target, targetfile, targetdirabs))
batchcmds:set_depcache(target:dependfile(targetfile))
end
end
diff --git a/xmake/rules/csharp/clean.lua b/xmake/rules/csharp/clean.lua
index 863e97b50..ce683268c 100644
--- a/xmake/rules/csharp/clean.lua
+++ b/xmake/rules/csharp/clean.lua
@@ -19,7 +19,7 @@
--
import("target.action.clean", {alias = "_do_clean_target"})
-import("csharp_common")
+import("modules.csharp_common", {rootdir = os.scriptdir(), alias = "csharp_common"})
function main(target, opt)
_do_clean_target(target)
diff --git a/xmake/rules/csharp/csharp_common.lua b/xmake/rules/csharp/csharp_common.lua
deleted file mode 100644
index 1df548c60..000000000
--- a/xmake/rules/csharp/csharp_common.lua
+++ /dev/null
@@ -1,160 +0,0 @@
---!A cross-platform build utility based on Lua
---
--- Licensed under the Apache License, Version 2.0 (the "License");
--- you may not use this file except in compliance with the License.
--- You may obtain a copy of the License at
---
--- http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
---
--- Copyright (C) 2015-present, Xmake Open Source Community.
---
--- @author JassJam
--- @file csharp_common.lua
---
-
-import("core.base.option")
-
-function _map_rid_arch(arch)
- arch = (arch or ""):lower()
- if arch == "x64" or arch == "x86_64" or arch == "amd64" then
- return "x64"
- elseif arch == "x86" or arch == "i386" then
- return "x86"
- elseif arch == "arm64" then
- return "arm64"
- elseif arch == "arm" or arch == "armv7" then
- return "arm"
- elseif arch == "riscv64" then
- return "riscv64"
- end
- return nil
-end
-
-function find_csproj(target)
- local csproj = target:data("csharp.csproj")
- if csproj then
- return csproj
- end
- for _, sourcefile in ipairs(target:sourcefiles()) do
- if path.extension(sourcefile):lower() == ".csproj" then
- local csprojabs = path.is_absolute(sourcefile) and sourcefile or path.absolute(sourcefile, os.projectdir())
- if os.isfile(csprojabs) then
- return csprojabs
- end
- end
- end
- return nil
-end
-
-function build_mode_to_configuration()
- local mode
- if type(get_config) == "function" then
- mode = get_config("mode")
- end
- if not mode and type(is_mode) == "function" then
- if is_mode("debug") then
- mode = "debug"
- elseif is_mode("release") then
- mode = "release"
- end
- end
- mode = mode or "release"
- local mode_lower = mode:lower()
- if mode_lower == "debug" then
- return "Debug"
- elseif mode_lower == "release" then
- return "Release"
- end
- return mode:sub(1, 1):upper() .. mode:sub(2)
-end
-
-function get_runtime_identifier(target)
- local rid = target:values("csharp.runtime_identifier")
- if type(rid) == "table" then
- rid = rid[1]
- end
- if rid and #rid > 0 then
- return rid
- end
- local arch = _map_rid_arch(target:arch())
- if not arch then
- return nil
- end
- local plat = target:plat()
- if plat == "windows" or plat == "mingw" or plat == "msys" or plat == "cygwin" then
- return "win-" .. arch
- elseif plat == "linux" then
- return "linux-" .. arch
- elseif plat == "macosx" then
- return "osx-" .. arch
- end
- return nil
-end
-
-function append_target_flags(target, argv)
- local flags = {}
- table.join2(flags, table.wrap(target:get("csflags")))
- table.join2(flags, table.wrap(target:get("ldflags")))
- table.join2(flags, table.wrap(target:get("arflags")))
- table.join2(flags, table.wrap(target:get("shflags")))
- for _, flag in ipairs(flags) do
- if flag and #flag > 0 then
- table.insert(argv, flag)
- end
- end
-end
-
-function get_dotnet_runopt(csprojfile)
- return {
- curdir = path.directory(csprojfile),
- envs = {
- DOTNET_NOLOGO = "1",
- DOTNET_CLI_TELEMETRY_OPTOUT = "1",
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE = "1",
- DOTNET_GENERATE_ASPNET_CERTIFICATE = "0",
- DOTNET_ADD_GLOBAL_TOOLS_TO_PATH = "0"
- }
- }
-end
-
-function get_dotnet_verbosity()
- if option.get("diagnosis") then
- return "diagnostic"
- end
- return "quiet"
-end
-
-function get_dotnet_program(target)
- local function _get_configured_program(toolkind)
- if target:get("toolset." .. toolkind) then
- local program = target:tool(toolkind)
- if program and #program > 0 then
- return program
- end
- end
- end
-
- local program = nil
- if target:is_binary() then
- program = _get_configured_program("ld")
- elseif target:is_shared() then
- program = _get_configured_program("sh")
- else
- program = _get_configured_program("cs")
- end
- if program then
- return program
- end
-
- program = target:tool("cs")
- if program and #program > 0 then
- return program
- end
- return "dotnet"
-end
diff --git a/xmake/rules/csharp/install.lua b/xmake/rules/csharp/install.lua
index 34109a054..32cc9e8e0 100644
--- a/xmake/rules/csharp/install.lua
+++ b/xmake/rules/csharp/install.lua
@@ -18,7 +18,7 @@
-- @file install.lua
--
-import("csharp_common")
+import("modules.csharp_common", {rootdir = os.scriptdir(), alias = "csharp_common"})
function main(target, opt)
local function _q(arg)
@@ -29,7 +29,7 @@ function main(target, opt)
return arg
end
- local csprojfile = assert(csharp_common.find_csproj(target), "target(%s): missing csharp .csproj file!", target:name())
+ local csprojfile = assert(csharp_common.find_or_generate_csproj(target), "target(%s): missing csharp .csproj file!", target:name())
local dotnet = csharp_common.get_dotnet_program(target)
local configuration = csharp_common.build_mode_to_configuration()
local verbosity = csharp_common.get_dotnet_verbosity()
diff --git a/xmake/rules/csharp/installcmd.lua b/xmake/rules/csharp/installcmd.lua
index 76c051a59..7e1e385b4 100644
--- a/xmake/rules/csharp/installcmd.lua
+++ b/xmake/rules/csharp/installcmd.lua
@@ -18,10 +18,10 @@
-- @file installcmd.lua
--
-import("csharp_common")
+import("modules.csharp_common", {rootdir = os.scriptdir(), alias = "csharp_common"})
function main(target, batchcmds, opt)
- local csprojfile = assert(csharp_common.find_csproj(target), "target(%s): missing csharp .csproj file!", target:name())
+ local csprojfile = assert(csharp_common.find_or_generate_csproj(target), "target(%s): missing csharp .csproj file!", target:name())
local dotnet = csharp_common.get_dotnet_program(target)
local configuration = csharp_common.build_mode_to_configuration()
local verbosity = csharp_common.get_dotnet_verbosity()
diff --git a/xmake/rules/csharp/load.lua b/xmake/rules/csharp/load.lua
index 0811cf8d0..14b53a1fa 100644
--- a/xmake/rules/csharp/load.lua
+++ b/xmake/rules/csharp/load.lua
@@ -18,6 +18,8 @@
-- @file load.lua
--
+import("modules.csharp_common", {rootdir = os.scriptdir(), alias = "csharp_common"})
+
function main(target)
local targetdir = target:targetdir()
if targetdir and path.filename(targetdir) ~= target:name() then
@@ -40,12 +42,21 @@ function main(target)
table.insert(csprojfiles, sourcefile)
end
end
- assert(#csprojfiles > 0, "target(%s): csharp requires one .csproj in add_files()!", target:name())
- assert(#csprojfiles == 1, "target(%s): csharp only supports one .csproj file!", target:name())
+ assert(#csprojfiles <= 1, "target(%s): csharp only supports one .csproj file!", target:name())
- local csprojfile = csprojfiles[1]
- local csprojabs = path.is_absolute(csprojfile) and csprojfile or path.absolute(csprojfile, os.projectdir())
- assert(os.isfile(csprojabs), "target(%s): csharp .csproj not found: %s", target:name(), csprojfile)
+ local csprojabs = nil
+ if #csprojfiles == 1 then
+ local csprojfile = csprojfiles[1]
+ local csprojpath = path.is_absolute(csprojfile) and csprojfile or path.absolute(csprojfile, os.projectdir())
+ if os.isfile(csprojpath) then
+ csprojabs = csprojpath
+ end
+ end
+
+ if not csprojabs then
+ csprojabs = csharp_common.find_or_generate_csproj(target, {skip_deps = true})
+ end
+ assert(csprojabs, "target(%s): csharp failed to resolve or generate .csproj file!", target:name())
if not target:get("filename") and not target:get("basename") then
target:set("basename", path.basename(csprojabs))
diff --git a/xmake/rules/csharp/modules/csharp_common.lua b/xmake/rules/csharp/modules/csharp_common.lua
new file mode 100644
index 000000000..dce35021a
--- /dev/null
+++ b/xmake/rules/csharp/modules/csharp_common.lua
@@ -0,0 +1,232 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author JassJam
+-- @file csharp_common.lua
+--
+
+import("core.base.option")
+import("core.project.config")
+import("csproj_generator", {rootdir = os.scriptdir(), alias = "generate_csproj"})
+
+function _is_csharp_target(target)
+ if target:rule("csharp") then
+ return true
+ end
+ for _, sourcefile in ipairs(target:sourcefiles()) do
+ local ext = path.extension(sourcefile):lower()
+ if ext == ".cs" or ext == ".csproj" then
+ return true
+ end
+ end
+ return false
+end
+
+function _generated_csproj_path(target)
+ local targetkey = target:fullname():replace("::", path.sep())
+ local csprojdir = path.join(config.directory(), "rules", "csharp", targetkey, target:plat(), target:arch())
+ local csprojname = target:name() .. ".csproj"
+ return path.join(csprojdir, csprojname)
+end
+
+function _map_rid_arch(arch)
+ arch = (arch or ""):lower()
+ if arch == "x64" or arch == "x86_64" or arch == "amd64" then
+ return "x64"
+ elseif arch == "x86" or arch == "i386" then
+ return "x86"
+ elseif arch == "arm64" then
+ return "arm64"
+ elseif arch == "arm" or arch == "armv7" then
+ return "arm"
+ elseif arch == "riscv64" then
+ return "riscv64"
+ end
+ return nil
+end
+
+function find_csproj(target)
+ local csproj = target:data("csharp.csproj")
+ if csproj then
+ return csproj
+ end
+ for _, sourcefile in ipairs(target:sourcefiles()) do
+ if path.extension(sourcefile):lower() == ".csproj" then
+ local csprojabs = path.is_absolute(sourcefile) and sourcefile or path.absolute(sourcefile, os.projectdir())
+ if os.isfile(csprojabs) then
+ return csprojabs
+ end
+ end
+ end
+ return nil
+end
+
+function find_or_generate_csproj(target, opt)
+ opt = opt or {}
+ local csproj = find_csproj(target)
+ local generated = target:data("csharp.csproj.generated")
+ local generated_with_deps = target:data("csharp.csproj.generated.with_deps")
+
+ -- prefer existing source .csproj directly
+ if csproj and not generated then
+ return csproj
+ end
+
+ -- generated .csproj in memory cache
+ if csproj and generated then
+ if opt.skip_deps or generated_with_deps then
+ return csproj
+ end
+ end
+
+ if not _is_csharp_target(target) then
+ return nil
+ end
+
+ local csprojfile = csproj or _generated_csproj_path(target)
+ local generated_now = false
+
+ -- in load phase (skip_deps), reuse existing generated file to avoid
+ -- touching mtime and causing unnecessary rebuilds.
+ if not (opt.skip_deps and os.isfile(csprojfile)) then
+ generate_csproj(target, csprojfile, table.join(opt, {
+ is_csharp_target = _is_csharp_target,
+ find_or_generate_csproj = find_or_generate_csproj
+ }))
+ generated_now = true
+ end
+
+ target:data_set("csharp.csproj", csprojfile)
+ target:data_set("csharp.csproj.generated", true)
+ if opt.skip_deps then
+ -- keep this conservative in load phase: build/install phase will
+ -- generate a deps-enabled project if needed, and content checks avoid
+ -- unnecessary rewrites.
+ if generated_now or generated_with_deps == nil then
+ target:data_set("csharp.csproj.generated.with_deps", false)
+ end
+ else
+ target:data_set("csharp.csproj.generated.with_deps", true)
+ end
+ return csprojfile
+end
+
+function build_mode_to_configuration()
+ local mode
+ if type(get_config) == "function" then
+ mode = get_config("mode")
+ end
+ if not mode and type(is_mode) == "function" then
+ if is_mode("debug") then
+ mode = "debug"
+ elseif is_mode("release") then
+ mode = "release"
+ end
+ end
+ mode = mode or "release"
+ local mode_lower = mode:lower()
+ if mode_lower == "debug" then
+ return "Debug"
+ elseif mode_lower == "release" then
+ return "Release"
+ end
+ return mode:sub(1, 1):upper() .. mode:sub(2)
+end
+
+function get_runtime_identifier(target)
+ local rid = target:values("csharp.runtime_identifier")
+ if type(rid) == "table" then
+ rid = rid[1]
+ end
+ if rid and #rid > 0 then
+ return rid
+ end
+ local arch = _map_rid_arch(target:arch())
+ if not arch then
+ return nil
+ end
+ local plat = target:plat()
+ if plat == "windows" or plat == "mingw" or plat == "msys" or plat == "cygwin" then
+ return "win-" .. arch
+ elseif plat == "linux" then
+ return "linux-" .. arch
+ elseif plat == "macosx" then
+ return "osx-" .. arch
+ end
+ return nil
+end
+
+function append_target_flags(target, argv)
+ local flags = {}
+ table.join2(flags, table.wrap(target:get("csflags")))
+ table.join2(flags, table.wrap(target:get("ldflags")))
+ table.join2(flags, table.wrap(target:get("arflags")))
+ table.join2(flags, table.wrap(target:get("shflags")))
+ for _, flag in ipairs(flags) do
+ if flag and #flag > 0 then
+ table.insert(argv, flag)
+ end
+ end
+end
+
+function get_dotnet_runopt(csprojfile)
+ return {
+ curdir = path.directory(csprojfile),
+ envs = {
+ DOTNET_NOLOGO = "1",
+ DOTNET_CLI_TELEMETRY_OPTOUT = "1",
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE = "1",
+ DOTNET_GENERATE_ASPNET_CERTIFICATE = "0",
+ DOTNET_ADD_GLOBAL_TOOLS_TO_PATH = "0"
+ }
+ }
+end
+
+function get_dotnet_verbosity()
+ if option.get("diagnosis") then
+ return "diagnostic"
+ end
+ return "quiet"
+end
+
+function get_dotnet_program(target)
+ local function _get_configured_program(toolkind)
+ if target:get("toolset." .. toolkind) then
+ local program = target:tool(toolkind)
+ if program and #program > 0 then
+ return program
+ end
+ end
+ end
+
+ local program = nil
+ if target:is_binary() then
+ program = _get_configured_program("ld")
+ elseif target:is_shared() then
+ program = _get_configured_program("sh")
+ else
+ program = _get_configured_program("cs")
+ end
+ if program then
+ return program
+ end
+
+ program = target:tool("cs")
+ if program and #program > 0 then
+ return program
+ end
+ return "dotnet"
+end
diff --git a/xmake/rules/csharp/modules/csproj_generator.lua b/xmake/rules/csharp/modules/csproj_generator.lua
new file mode 100644
index 000000000..0f94ed806
--- /dev/null
+++ b/xmake/rules/csharp/modules/csproj_generator.lua
@@ -0,0 +1,301 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author JassJam
+-- @file csproj_generator.lua
+--
+
+import("properties", {rootdir = os.scriptdir(), alias = "csharp_properties"})
+import("itemgroups", {rootdir = os.scriptdir(), alias = "csharp_itemgroups"})
+
+function _xml_escape(value)
+ value = tostring(value or "")
+ value = value:gsub("&", "&")
+ value = value:gsub("<", "<")
+ value = value:gsub(">", ">")
+ value = value:gsub("\"", """)
+ value = value:gsub("'", "'")
+ return value
+end
+
+function _format_attributes(attrs)
+ if type(attrs) ~= "table" then
+ return ""
+ end
+ local keys = {}
+ for key, value in pairs(attrs) do
+ if value ~= nil and value ~= "" then
+ table.insert(keys, key)
+ end
+ end
+ table.sort(keys)
+ if #keys == 0 then
+ return ""
+ end
+ local chunks = {}
+ for _, key in ipairs(keys) do
+ table.insert(chunks, string.format(" %s=\"%s\"", key, _xml_escape(attrs[key])))
+ end
+ return table.concat(chunks)
+end
+
+function _get_csharp_value(target, name, defaultval)
+ local val = target:values(name)
+ if type(val) == "table" then
+ val = val[1]
+ end
+ if val == nil or val == "" then
+ return defaultval
+ end
+ return val
+end
+
+function _resolve_registry_value(entry, target, context)
+ if entry.resolve then
+ return entry.resolve(context)
+ end
+ if entry.value_type == "list" then
+ local values = table.wrap(target:values(entry.lua_key))
+ if #values == 0 and entry.default ~= nil then
+ values = table.wrap(entry.default)
+ end
+ if #values > 0 then
+ local items = {}
+ for _, value in ipairs(values) do
+ if value ~= nil and value ~= "" then
+ table.insert(items, tostring(value))
+ end
+ end
+ if #items > 0 then
+ return table.concat(items, entry.sep or ";")
+ end
+ end
+ return nil
+ end
+ return _get_csharp_value(target, entry.lua_key, entry.default)
+end
+
+function _collect_project_attributes(target, context, registry_entries)
+ local attrs = {}
+ for _, entry in ipairs(registry_entries) do
+ if entry.kind == "project_attribute" then
+ if not entry.when or entry.when(context) then
+ local value = _resolve_registry_value(entry, target, context)
+ if value ~= nil and value ~= "" then
+ attrs[entry.attr] = value
+ end
+ end
+ end
+ end
+ return attrs
+end
+
+function _collect_property_entries(target, context, registry_entries)
+ local entries = {}
+ for _, entry in ipairs(registry_entries) do
+ if entry.kind == "property" then
+ if not entry.when or entry.when(context) then
+ local value = _resolve_registry_value(entry, target, context)
+ if value ~= nil and value ~= "" then
+ table.insert(entries, {xml = entry.xml, value = value})
+ end
+ end
+ end
+ end
+ return entries
+end
+
+function _normalize_item_entry(item, default_xml)
+ if type(item) == "string" then
+ return {xml = default_xml, attrs = {Include = item}}
+ elseif type(item) ~= "table" then
+ return nil
+ end
+ local xml = item.xml or default_xml
+ if not xml or #tostring(xml) == 0 then
+ return nil
+ end
+ local attrs = item.attrs
+ if type(attrs) ~= "table" then
+ attrs = {}
+ for key, value in pairs(item) do
+ if type(key) == "string" and key ~= "xml" and key ~= "value" and key ~= "attrs" then
+ attrs[key] = value
+ end
+ end
+ end
+ return {xml = xml, attrs = attrs, value = item.value}
+end
+
+function _collect_item_groups(context, registry_entries)
+ local groups = {}
+ local groupmap = {}
+ function _group(name)
+ local g = groupmap[name]
+ if not g then
+ g = {name = name, items = {}}
+ groupmap[name] = g
+ table.insert(groups, g)
+ end
+ return g
+ end
+ for _, entry in ipairs(registry_entries) do
+ if entry.kind == "item" then
+ if not entry.when or entry.when(context) then
+ for _, item in ipairs(table.wrap(entry.resolve_items and entry.resolve_items(context) or {})) do
+ local normalized = _normalize_item_entry(item, entry.xml)
+ if normalized then
+ table.insert(_group(entry.group or entry.xml).items, normalized)
+ end
+ end
+ end
+ end
+ end
+ return groups
+end
+
+function _is_valid_property_name(name)
+ return type(name) == "string" and name:match("^[A-Za-z_][A-Za-z0-9_.-]*$") ~= nil
+end
+
+function _add_custom_property(entries, name, value)
+ if not _is_valid_property_name(name) then
+ return
+ end
+ if value == nil then
+ return
+ end
+ if type(value) == "table" then
+ local values = {}
+ for _, v in ipairs(value) do
+ if v ~= nil and v ~= "" then
+ table.insert(values, tostring(v))
+ end
+ end
+ if #values == 0 then
+ return
+ end
+ value = table.concat(values, ";")
+ else
+ value = tostring(value)
+ end
+ if #value == 0 then
+ return
+ end
+ table.insert(entries, {xml = name, value = value})
+end
+
+function _add_custom_properties_from_item(entries, item)
+ if type(item) == "string" then
+ local name, value = item:match("^%s*([^=]+)%s*=(.*)$")
+ if name then
+ _add_custom_property(entries, name:trim(), value)
+ end
+ return
+ end
+ if type(item) ~= "table" then
+ return
+ end
+ if item.name then
+ _add_custom_property(entries, tostring(item.name), item.value)
+ return
+ end
+ local keys = {}
+ for k in pairs(item) do
+ if type(k) == "string" then
+ table.insert(keys, k)
+ end
+ end
+ table.sort(keys)
+ for _, key in ipairs(keys) do
+ _add_custom_property(entries, key, item[key])
+ end
+end
+
+function _collect_custom_property_entries(target)
+ local entries = {}
+ for _, item in ipairs(table.wrap(target:values("csharp.properties"))) do
+ _add_custom_properties_from_item(entries, item)
+ end
+ return entries
+end
+
+function _render_property_group(lines, entries)
+ if #entries == 0 then
+ return
+ end
+ table.insert(lines, " ")
+ for _, entry in ipairs(entries) do
+ table.insert(lines, string.format(" <%s>%s%s>", entry.xml, _xml_escape(entry.value), entry.xml))
+ end
+ table.insert(lines, " ")
+end
+
+function _render_item_groups(lines, item_groups)
+ for _, group in ipairs(item_groups) do
+ if #group.items > 0 then
+ table.insert(lines, " ")
+ for _, item in ipairs(group.items) do
+ local attrs = _format_attributes(item.attrs)
+ if item.value ~= nil and item.value ~= "" then
+ table.insert(lines, string.format(" <%s%s>%s%s>", item.xml, attrs, _xml_escape(item.value), item.xml))
+ else
+ table.insert(lines, string.format(" <%s%s />", item.xml, attrs))
+ end
+ end
+ table.insert(lines, " ")
+ end
+ end
+end
+
+function main(target, csprojfile, opt)
+ opt = opt or {}
+
+ local csprojdir = path.directory(csprojfile)
+ local context = {
+ target = target,
+ csprojfile = csprojfile,
+ csprojdir = csprojdir,
+ opt = opt
+ }
+
+ local property_registry_entries = csharp_properties()
+ local item_registry_entries = csharp_itemgroups()
+
+ local project_attributes = _collect_project_attributes(target, context, property_registry_entries)
+ local property_entries = _collect_property_entries(target, context, property_registry_entries)
+ local custom_property_entries = _collect_custom_property_entries(target)
+ table.join2(property_entries, custom_property_entries)
+
+ local item_groups = _collect_item_groups(context, item_registry_entries)
+ local lines = {}
+
+ table.insert(lines, string.format("", _format_attributes(project_attributes)))
+ _render_property_group(lines, property_entries)
+ _render_item_groups(lines, item_groups)
+ table.insert(lines, "")
+
+ local content = table.concat(lines, "\n") .. "\n"
+
+ os.mkdir(csprojdir)
+ local oldcontent = nil
+ if os.isfile(csprojfile) then
+ oldcontent = io.readfile(csprojfile)
+ end
+ if oldcontent ~= content then
+ io.writefile(csprojfile, content)
+ end
+end
diff --git a/xmake/rules/csharp/modules/itemgroups.lua b/xmake/rules/csharp/modules/itemgroups.lua
new file mode 100644
index 000000000..6d105fd45
--- /dev/null
+++ b/xmake/rules/csharp/modules/itemgroups.lua
@@ -0,0 +1,166 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author JassJam
+-- @file itemsgroups.lua
+--
+
+local function _normalize_relative(fromdir, targetpath)
+ local relpath = path.relative(targetpath, fromdir) or targetpath
+ if os.host() == "windows" then
+ relpath = relpath:gsub("\\", "/")
+ end
+ return relpath
+end
+
+local function _collect_cs_sourcefiles(context)
+ local csfiles = {}
+ for _, sourcefile in ipairs(context.target:sourcefiles()) do
+ if path.extension(sourcefile):lower() == ".cs" then
+ local sourceabs = path.is_absolute(sourcefile) and sourcefile or path.absolute(sourcefile, os.projectdir())
+ table.insert(csfiles, _normalize_relative(context.csprojdir, sourceabs))
+ end
+ end
+ table.sort(csfiles)
+ return table.unique(csfiles)
+end
+
+local function _collect_project_references(context)
+ if context.opt.skip_deps then
+ return {}
+ end
+ local references = {}
+ for _, depname in ipairs(table.wrap(context.target:get("deps"))) do
+ local dep = context.target:dep(depname)
+ if dep and context.opt.is_csharp_target and context.opt.is_csharp_target(dep) then
+ local depcsproj = context.opt.find_or_generate_csproj and context.opt.find_or_generate_csproj(dep)
+ if depcsproj then
+ table.insert(references, _normalize_relative(context.csprojdir, depcsproj))
+ end
+ end
+ end
+ table.sort(references)
+ return table.unique(references)
+end
+
+local function _get_nuget_info(pkg)
+ local requirestr = pkg:requirestr() or ""
+ local splitinfo = requirestr:trim():split("%s+")
+ if #splitinfo == 0 then
+ return nil
+ end
+
+ local pkgname = splitinfo[1]
+ if pkgname:find("::", 1, true) then
+ pkgname = pkgname:split("::", {plain = true})
+ pkgname = pkgname[#pkgname]
+ end
+ local pkgname_raw = pkgname:match("(.-)%[.*%]$")
+ if pkgname_raw and #pkgname_raw > 0 then
+ pkgname = pkgname_raw
+ end
+ if not pkgname or #pkgname == 0 then
+ return nil
+ end
+
+ local version
+ local versionobj = pkg:version()
+ if versionobj then
+ version = tostring(versionobj)
+ end
+ if not version and #splitinfo > 1 then
+ local require_version = table.concat(table.slice(splitinfo, 2), " ")
+ if require_version ~= "latest" then
+ version = require_version
+ end
+ end
+ return pkgname, version
+end
+
+local function _collect_nuget_references(context)
+ local versions = {}
+ for _, pkg in ipairs(context.target:orderpkgs()) do
+ local namespace = pkg:namespace()
+ local requirestr = pkg:requirestr() or ""
+ if namespace == "nuget" or requirestr:startswith("nuget::") then
+ local pkgname, version = _get_nuget_info(pkg)
+ if pkgname then
+ if version or versions[pkgname] == nil then
+ versions[pkgname] = version or false
+ end
+ end
+ end
+ end
+
+ local references = {}
+ for pkgname, version in pairs(versions) do
+ table.insert(references, {name = pkgname, version = version or nil})
+ end
+ table.sort(references, function (a, b) return a.name < b.name end)
+ return references
+end
+
+function main()
+ local entries = {}
+ local function register(entry)
+ table.insert(entries, entry)
+ end
+
+ register({
+ kind = "item",
+ group = "compile",
+ xml = "Compile",
+ resolve_items = function (context)
+ local items = {}
+ for _, sourcefile in ipairs(_collect_cs_sourcefiles(context)) do
+ table.insert(items, {attrs = {Include = sourcefile}})
+ end
+ return items
+ end
+ })
+
+ register({
+ kind = "item",
+ group = "project_reference",
+ xml = "ProjectReference",
+ resolve_items = function (context)
+ local items = {}
+ for _, reffile in ipairs(_collect_project_references(context)) do
+ table.insert(items, {attrs = {Include = reffile}})
+ end
+ return items
+ end
+ })
+
+ register({
+ kind = "item",
+ group = "package_reference",
+ xml = "PackageReference",
+ resolve_items = function (context)
+ local items = {}
+ for _, pkginfo in ipairs(_collect_nuget_references(context)) do
+ local attrs = {Include = pkginfo.name}
+ if pkginfo.version then
+ attrs.Version = pkginfo.version
+ end
+ table.insert(items, {attrs = attrs})
+ end
+ return items
+ end
+ })
+
+ return entries
+end
diff --git a/xmake/rules/csharp/modules/properties.lua b/xmake/rules/csharp/modules/properties.lua
new file mode 100644
index 000000000..b8d30f1e9
--- /dev/null
+++ b/xmake/rules/csharp/modules/properties.lua
@@ -0,0 +1,307 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author JassJam
+-- @file properties.lua
+--
+
+function _has_target_frameworks(context)
+ return #table.wrap(context.target:values("csharp.target_frameworks")) > 0
+end
+
+local _default_target_framework_cached = {}
+
+function _first(value)
+ if type(value) == "table" then
+ return value[1]
+ end
+ return value
+end
+
+function _get_target_value(target, name)
+ return _first(target:values(name))
+end
+
+function _collect_strings(result, value)
+ if value == nil then
+ return
+ end
+ if type(value) == "table" then
+ for _, item in ipairs(value) do
+ _collect_strings(result, item)
+ end
+ else
+ local sval = tostring(value)
+ if #sval > 0 then
+ table.insert(result, sval)
+ end
+ end
+end
+
+function _extract_define_symbol(define)
+ define = tostring(define):trim()
+ if #define == 0 then
+ return nil
+ end
+ local symbol = define:split("=", {plain = true})[1]
+ if not symbol then
+ return nil
+ end
+ symbol = symbol:trim()
+ if symbol:match("^[A-Za-z_][A-Za-z0-9_]*$") then
+ return symbol
+ end
+ return nil
+end
+
+function _resolve_define_constants(context)
+ local target = context.target
+ local constants = {}
+
+ _collect_strings(constants, target:get("defines"))
+ local opt_defines = target:get_from("defines", "option::*")
+ if opt_defines then
+ _collect_strings(constants, opt_defines)
+ end
+
+ local symbols = {}
+ for _, define in ipairs(constants) do
+ local symbol = _extract_define_symbol(define)
+ if symbol then
+ table.insert(symbols, symbol)
+ end
+ end
+ symbols = table.unique(symbols)
+ if #symbols > 0 then
+ return table.concat(symbols, ";")
+ end
+end
+
+function _get_default_target_framework(context)
+ local dotnet = _first(context.target:get("toolset.cs")) or "dotnet"
+ dotnet = tostring(dotnet)
+ local cached = _default_target_framework_cached[dotnet]
+ if cached ~= nil then
+ return cached
+ end
+
+ local major = nil
+ local sdks = try { function ()
+ return os.iorunv(dotnet, {"--list-sdks"})
+ end }
+ if sdks then
+ for line in sdks:gmatch("[^\r\n]+") do
+ local line_major = tonumber(line:match("^%s*(%d+)%.%d+%.%d+"))
+ if line_major and (not major or line_major > major) then
+ major = line_major
+ end
+ end
+ end
+ if not major then
+ local version = try { function ()
+ return os.iorunv(dotnet, {"--version"})
+ end }
+ if version then
+ major = tonumber(version:match("^%s*(%d+)"))
+ end
+ end
+
+ local target_framework = major and ("net" .. tostring(major) .. ".0") or "net8.0"
+ _default_target_framework_cached[dotnet] = target_framework
+ return target_framework
+end
+
+function _resolve_target_framework(context)
+ local target_framework = _get_target_value(context.target, "csharp.target_framework")
+ if target_framework ~= nil and #tostring(target_framework) > 0 then
+ return target_framework
+ end
+ return _get_default_target_framework(context)
+end
+
+function _resolve_assembly_name(context)
+ local basename = context.target:basename()
+ if basename ~= nil and #tostring(basename) > 0 then
+ return basename
+ end
+end
+
+function _resolve_optimize(context)
+ local optimize = _first(context.target:get("optimize"))
+ if optimize ~= nil then
+ return optimize == "none" and "false" or "true"
+ end
+end
+
+function _resolve_debug_symbols(context)
+ local symbols = _first(context.target:get("symbols"))
+ if symbols ~= nil then
+ return symbols == "none" and "false" or "true"
+ end
+end
+
+function _resolve_debug_type(context)
+ local symbols = _first(context.target:get("symbols"))
+ if symbols and symbols ~= "none" then
+ return "portable"
+ end
+end
+
+function _resolve_platform_target(context)
+ local arch = (context.target:arch() or ""):lower()
+ local mapping = {
+ x86_64 = "x64",
+ amd64 = "x64",
+ x64 = "x64",
+ i386 = "x86",
+ x86 = "x86",
+ arm64 = "arm64",
+ arm = "arm",
+ armv7 = "arm"
+ }
+ return mapping[arch]
+end
+
+function _resolve_warning_level(context)
+ local warnings = _first(context.target:get("warnings"))
+ local mapping = {
+ none = "0",
+ less = "2",
+ more = "3",
+ all = "4",
+ allextra = "4",
+ everything = "4",
+ error = "4"
+ }
+ return warnings and mapping[warnings]
+end
+
+function _resolve_treat_warnings_as_errors(context)
+ local warnings = _first(context.target:get("warnings"))
+ if warnings == "error" then
+ return "true"
+ end
+end
+
+function _register_property(register, suffix, xml, default, extra)
+ local entry = table.join({
+ kind = "property",
+ xml = xml,
+ lua_key = "csharp." .. suffix,
+ default = default
+ }, extra or {})
+ register(entry)
+end
+
+function _register_list_property(register, suffix, xml, extra)
+ local entry = table.join({
+ kind = "property",
+ xml = xml,
+ lua_key = "csharp." .. suffix,
+ value_type = "list",
+ sep = ";"
+ }, extra or {})
+ register(entry)
+end
+
+function main()
+ local entries = {}
+ function register(entry)
+ table.insert(entries, entry)
+ end
+
+ register({kind = "project_attribute", attr = "Sdk", lua_key = "csharp.sdk", default = "Microsoft.NET.Sdk"})
+ register({kind = "property", xml = "OutputType", resolve = function (context)
+ return context.target:is_binary() and "Exe" or "Library"
+ end})
+ _register_list_property(register, "target_frameworks", "TargetFrameworks", {when = _has_target_frameworks})
+ register({kind = "property", xml = "TargetFramework", resolve = _resolve_target_framework, when = function (context)
+ return not _has_target_frameworks(context)
+ end})
+
+ _register_property(register, "implicit_usings", "ImplicitUsings", "enable")
+ _register_property(register, "nullable", "Nullable", "enable")
+ _register_property(register, "lang_version", "LangVersion")
+ _register_property(register, "enable_default_compile_items", "EnableDefaultCompileItems", "false")
+ _register_property(register, "enable_default_embedded_resource_items", "EnableDefaultEmbeddedResourceItems")
+ _register_property(register, "enable_default_none_items", "EnableDefaultNoneItems")
+ _register_property(register, "root_namespace", "RootNamespace")
+ register({kind = "property", xml = "AssemblyName", resolve = _resolve_assembly_name})
+ _register_property(register, "generate_assembly_info", "GenerateAssemblyInfo")
+ _register_property(register, "deterministic", "Deterministic")
+ register({kind = "property", xml = "Optimize", resolve = _resolve_optimize})
+ register({kind = "property", xml = "PlatformTarget", resolve = _resolve_platform_target})
+ _register_property(register, "prefer_32bit", "Prefer32Bit")
+ _register_property(register, "allow_unsafe_blocks", "AllowUnsafeBlocks")
+ _register_property(register, "check_for_overflow_underflow", "CheckForOverflowUnderflow")
+ register({kind = "property", xml = "WarningLevel", resolve = _resolve_warning_level})
+ _register_property(register, "analysis_level", "AnalysisLevel")
+ _register_property(register, "enable_net_analyzers", "EnableNETAnalyzers")
+ _register_property(register, "enforce_code_style_in_build", "EnforceCodeStyleInBuild")
+ register({kind = "property", xml = "TreatWarningsAsErrors", resolve = _resolve_treat_warnings_as_errors})
+ _register_list_property(register, "warnings_as_errors", "WarningsAsErrors")
+ _register_list_property(register, "warnings_not_as_errors", "WarningsNotAsErrors")
+ register({kind = "property", xml = "DefineConstants", resolve = _resolve_define_constants})
+ _register_property(register, "error_log", "ErrorLog")
+ register({kind = "property", xml = "DebugType", resolve = _resolve_debug_type})
+ register({kind = "property", xml = "DebugSymbols", resolve = _resolve_debug_symbols})
+ _register_property(register, "generate_documentation_file", "GenerateDocumentationFile")
+ _register_property(register, "documentation_file", "DocumentationFile")
+
+ _register_property(register, "runtime_identifier", "RuntimeIdentifier")
+ _register_list_property(register, "runtime_identifiers", "RuntimeIdentifiers")
+ _register_property(register, "self_contained", "SelfContained")
+ _register_property(register, "use_app_host", "UseAppHost")
+ _register_property(register, "roll_forward", "RollForward")
+ _register_property(register, "publish_single_file", "PublishSingleFile")
+ _register_property(register, "publish_trimmed", "PublishTrimmed")
+ _register_property(register, "trim_mode", "TrimMode")
+ _register_property(register, "publish_ready_to_run", "PublishReadyToRun")
+ _register_property(register, "invariant_globalization", "InvariantGlobalization")
+ _register_property(register, "include_native_libraries_for_self_extract", "IncludeNativeLibrariesForSelfExtract")
+ _register_property(register, "enable_compression_in_single_file", "EnableCompressionInSingleFile")
+ _register_property(register, "publish_aot", "PublishAot")
+ _register_property(register, "strip_symbols", "StripSymbols")
+ _register_property(register, "enable_trim_analyzer", "EnableTrimAnalyzer")
+ _register_property(register, "json_serializer_is_reflection_enabled_by_default", "JsonSerializerIsReflectionEnabledByDefault")
+ _register_list_property(register, "satellite_resource_languages", "SatelliteResourceLanguages")
+
+ _register_property(register, "version", "Version")
+ _register_property(register, "assembly_version", "AssemblyVersion")
+ _register_property(register, "file_version", "FileVersion")
+ _register_property(register, "informational_version", "InformationalVersion")
+ _register_property(register, "package_id", "PackageId")
+ _register_property(register, "authors", "Authors")
+ _register_property(register, "company", "Company")
+ _register_property(register, "product", "Product")
+ _register_property(register, "description", "Description")
+ _register_property(register, "copyright", "Copyright")
+ _register_property(register, "repository_url", "RepositoryUrl")
+ _register_property(register, "repository_type", "RepositoryType")
+ _register_property(register, "package_license_expression", "PackageLicenseExpression")
+ _register_property(register, "package_project_url", "PackageProjectUrl")
+ _register_property(register, "neutral_language", "NeutralLanguage")
+ _register_property(register, "enable_preview_features", "EnablePreviewFeatures")
+
+ _register_property(register, "generate_runtime_configuration_files", "GenerateRuntimeConfigurationFiles")
+ _register_property(register, "copy_local_lock_file_assemblies", "CopyLocalLockFileAssemblies")
+ _register_property(register, "append_target_framework_to_output_path", "AppendTargetFrameworkToOutputPath", "false")
+ _register_property(register, "append_runtime_identifier_to_output_path", "AppendRuntimeIdentifierToOutputPath", "false")
+ _register_property(register, "produce_reference_assembly", "ProduceReferenceAssembly")
+ _register_property(register, "disable_implicit_framework_references", "DisableImplicitFrameworkReferences")
+ _register_property(register, "generate_target_framework_attribute", "GenerateTargetFrameworkAttribute")
+ return entries
+end
diff --git a/xmake/rules/csharp/xmake.lua b/xmake/rules/csharp/xmake.lua
index 5d358e099..b5e76dfa9 100644
--- a/xmake/rules/csharp/xmake.lua
+++ b/xmake/rules/csharp/xmake.lua
@@ -24,10 +24,87 @@
-- target("app")
-- set_kind("binary") -- or "shared"
-- add_rules("csharp")
--- add_files("src/*.cs", "src/app.csproj")
+-- add_files("src/*.cs")
--
--- -- optional: override runtime identifier (arch-specific) for self-contained publish (e.g. "win-x64", "linux-x64", "osx-x64", "osx-arm64")
--- -- add_values("csharp.runtime_identifier", "win-x64")
+-- -- optional: if a .csproj is provided, xmake uses it directly.
+-- -- otherwise xmake auto-generates one in .xmake/... for dotnet build/publish.
+-- -- add_files("src/app.csproj")
+--
+-- -- add_values("csharp.", ) -- use one the properties below
+-- -- add_values("csharp.properties", "AnotherProperty=Value") -- declare a custom property
+--
+-- -- mapped csharp properties (lua key => generated xml):
+-- -- https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#pack-target
+-- -- csharp.sdk => (project attribute) (defaults to "Microsoft.NET.Sdk")
+-- -- csharp.target_frameworks => (list, ';' separated)
+-- -- csharp.target_framework => (defaults to latest installed dotnet sdk major, e.g. net8.0)
+-- -- csharp.implicit_usings =>
+-- -- csharp.nullable =>
+-- -- csharp.lang_version =>
+-- -- csharp.enable_default_compile_items =>
+-- -- csharp.enable_default_embedded_resource_items =>
+-- -- csharp.enable_default_none_items =>
+-- -- csharp.root_namespace =>
+-- -- default/derived: set_basename(...) =>
+-- -- csharp.generate_assembly_info =>
+-- -- csharp.deterministic =>
+-- -- default/derived: set_optimize(...) =>
+-- -- default/derived: get_plat()/get_arch() =>
+-- -- csharp.prefer_32bit =>
+-- -- csharp.allow_unsafe_blocks =>
+-- -- csharp.check_for_overflow_underflow =>
+-- -- default/derived: set_warnings(...) =>
+-- -- csharp.analysis_level =>
+-- -- csharp.enable_net_analyzers =>
+-- -- csharp.enforce_code_style_in_build =>
+-- -- default/derived: set_warnings("error") => true
+-- -- csharp.warnings_as_errors => (list, ';' separated)
+-- -- csharp.warnings_not_as_errors => (list, ';' separated)
+-- -- default/derived: add_defines(...) =>
+-- -- csharp.error_log =>
+-- -- default/derived: set_symbols(...) => /
+-- -- csharp.generate_documentation_file =>
+-- -- csharp.documentation_file =>
+-- -- csharp.runtime_identifier =>
+-- -- csharp.runtime_identifiers => (list, ';' separated)
+-- -- csharp.self_contained =>
+-- -- csharp.use_app_host =>
+-- -- csharp.roll_forward =>
+-- -- csharp.publish_single_file =>
+-- -- csharp.publish_trimmed =>
+-- -- csharp.trim_mode =>
+-- -- csharp.publish_ready_to_run =>
+-- -- csharp.invariant_globalization =>
+-- -- csharp.include_native_libraries_for_self_extract =>
+-- -- csharp.enable_compression_in_single_file =>
+-- -- csharp.publish_aot =>
+-- -- csharp.strip_symbols =>
+-- -- csharp.enable_trim_analyzer =>
+-- -- csharp.json_serializer_is_reflection_enabled_by_default =>
+-- -- csharp.satellite_resource_languages => (list, ';' separated)
+-- -- csharp.version =>
+-- -- csharp.assembly_version =>
+-- -- csharp.file_version =>
+-- -- csharp.informational_version =>
+-- -- csharp.package_id =>
+-- -- csharp.authors =>
+-- -- csharp.company =>
+-- -- csharp.product =>
+-- -- csharp.description =>
+-- -- csharp.copyright =>
+-- -- csharp.repository_url =>
+-- -- csharp.repository_type =>
+-- -- csharp.package_license_expression =>
+-- -- csharp.package_project_url =>
+-- -- csharp.neutral_language =>
+-- -- csharp.enable_preview_features =>
+-- -- csharp.generate_runtime_configuration_files =>
+-- -- csharp.copy_local_lock_file_assemblies =>
+-- -- csharp.append_target_framework_to_output_path => (default: false)
+-- -- csharp.append_runtime_identifier_to_output_path => (default: false)
+-- -- csharp.produce_reference_assembly =>
+-- -- csharp.disable_implicit_framework_references =>
+-- -- csharp.generate_target_framework_attribute =>
-- @endcode
--
rule("csharp")
--
cgit v1.3.1