diff options
58 files changed, 1997 insertions, 169 deletions
diff --git a/tests/projects/csharp/console/.gitignore b/tests/projects/csharp/console/.gitignore new file mode 100644 index 000000000..6b8d3af38 --- /dev/null +++ b/tests/projects/csharp/console/.gitignore @@ -0,0 +1,6 @@ +.xmake/ +build/ +bin/ +obj/ +vsxmake*/ +.vs/ diff --git a/tests/projects/csharp/console/src/Program.cs b/tests/projects/csharp/console/src/Program.cs new file mode 100644 index 000000000..c7d5272d9 --- /dev/null +++ b/tests/projects/csharp/console/src/Program.cs @@ -0,0 +1,2 @@ +Console.WriteLine("hello xmake!"); + diff --git a/tests/projects/csharp/console/test.lua b/tests/projects/csharp/console/test.lua new file mode 100644 index 000000000..d909316b7 --- /dev/null +++ b/tests/projects/csharp/console/test.lua @@ -0,0 +1,13 @@ +import("detect.sdks.find_dotnet") + +function test_build(t) + if is_subhost("msys") then + return t:skip("csharp not supported on msys") + end + local dotnet = find_dotnet() + if dotnet and dotnet.sdkver then + t:build() + else + return t:skip("dotnet sdk not found") + end +end diff --git a/tests/projects/csharp/console/xmake.lua b/tests/projects/csharp/console/xmake.lua new file mode 100644 index 000000000..3288c96a8 --- /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") diff --git a/tests/projects/csharp/console_with_runtime_json/.gitignore b/tests/projects/csharp/console_with_runtime_json/.gitignore new file mode 100644 index 000000000..53e577525 --- /dev/null +++ b/tests/projects/csharp/console_with_runtime_json/.gitignore @@ -0,0 +1,7 @@ +.xmake/ +build/ +bin/ +obj/ +vsxmake*/ +.vs/ + diff --git a/tests/projects/csharp/console_with_runtime_json/src/Program.cs b/tests/projects/csharp/console_with_runtime_json/src/Program.cs new file mode 100644 index 000000000..fd836a339 --- /dev/null +++ b/tests/projects/csharp/console_with_runtime_json/src/Program.cs @@ -0,0 +1,15 @@ +using System.Text.Json; + +var runtimeFile = Path.Combine(Directory.GetCurrentDirectory(), "runtime.json"); +if (!File.Exists(runtimeFile)) { + Console.WriteLine("runtime.json missing"); + return; +} + +var json = File.ReadAllText(runtimeFile); +using var doc = JsonDocument.Parse(json); +var runtime = doc.RootElement.TryGetProperty("runtime", out var value) + ? value.GetString() + : "unknown"; + +Console.WriteLine($"runtime={runtime}"); diff --git a/tests/projects/csharp/console_with_runtime_json/src/runtime.json b/tests/projects/csharp/console_with_runtime_json/src/runtime.json new file mode 100644 index 000000000..15bec810d --- /dev/null +++ b/tests/projects/csharp/console_with_runtime_json/src/runtime.json @@ -0,0 +1,7 @@ +{ + "runtime": "xmake", + "featureFlags": { + "sample": true + } +} + diff --git a/tests/projects/csharp/console_with_runtime_json/test.lua b/tests/projects/csharp/console_with_runtime_json/test.lua new file mode 100644 index 000000000..d909316b7 --- /dev/null +++ b/tests/projects/csharp/console_with_runtime_json/test.lua @@ -0,0 +1,13 @@ +import("detect.sdks.find_dotnet") + +function test_build(t) + if is_subhost("msys") then + return t:skip("csharp not supported on msys") + end + local dotnet = find_dotnet() + if dotnet and dotnet.sdkver then + t:build() + else + return t:skip("dotnet sdk not found") + end +end diff --git a/tests/projects/csharp/console_with_runtime_json/xmake.lua b/tests/projects/csharp/console_with_runtime_json/xmake.lua new file mode 100644 index 000000000..979f74307 --- /dev/null +++ b/tests/projects/csharp/console_with_runtime_json/xmake.lua @@ -0,0 +1,7 @@ +add_rules("mode.debug", "mode.release") + +target("app") + set_kind("binary") + add_rules("csharp") + add_files("src/Program.cs") + set_rundir("src") diff --git a/tests/projects/csharp/multiple_library/.gitignore b/tests/projects/csharp/multiple_library/.gitignore new file mode 100644 index 000000000..6b8d3af38 --- /dev/null +++ b/tests/projects/csharp/multiple_library/.gitignore @@ -0,0 +1,6 @@ +.xmake/ +build/ +bin/ +obj/ +vsxmake*/ +.vs/ diff --git a/tests/projects/csharp/multiple_library/src/libalpha/Alpha.cs b/tests/projects/csharp/multiple_library/src/libalpha/Alpha.cs new file mode 100644 index 000000000..a10ed6d73 --- /dev/null +++ b/tests/projects/csharp/multiple_library/src/libalpha/Alpha.cs @@ -0,0 +1,7 @@ +namespace LibAlpha; + +public static class Alpha { + public static string Message() { + return "alpha"; + } +} diff --git a/tests/projects/csharp/multiple_library/src/libbeta/Beta.cs b/tests/projects/csharp/multiple_library/src/libbeta/Beta.cs new file mode 100644 index 000000000..c96944dc5 --- /dev/null +++ b/tests/projects/csharp/multiple_library/src/libbeta/Beta.cs @@ -0,0 +1,9 @@ +using LibAlpha; + +namespace LibBeta; + +public static class Beta { + public static string Message() { + return Alpha.Message() + "+beta"; + } +} diff --git a/tests/projects/csharp/multiple_library/src/sample/Program.cs b/tests/projects/csharp/multiple_library/src/sample/Program.cs new file mode 100644 index 000000000..2292a8af9 --- /dev/null +++ b/tests/projects/csharp/multiple_library/src/sample/Program.cs @@ -0,0 +1,3 @@ +using LibBeta; + +Console.WriteLine(Beta.Message());
\ No newline at end of file diff --git a/tests/projects/csharp/multiple_library/test.lua b/tests/projects/csharp/multiple_library/test.lua new file mode 100644 index 000000000..d909316b7 --- /dev/null +++ b/tests/projects/csharp/multiple_library/test.lua @@ -0,0 +1,13 @@ +import("detect.sdks.find_dotnet") + +function test_build(t) + if is_subhost("msys") then + return t:skip("csharp not supported on msys") + end + local dotnet = find_dotnet() + if dotnet and dotnet.sdkver then + t:build() + else + return t:skip("dotnet sdk not found") + end +end diff --git a/tests/projects/csharp/multiple_library/xmake.lua b/tests/projects/csharp/multiple_library/xmake.lua new file mode 100644 index 000000000..db3f65811 --- /dev/null +++ b/tests/projects/csharp/multiple_library/xmake.lua @@ -0,0 +1,18 @@ +add_rules("mode.debug", "mode.release") + +target("libalpha") + set_kind("shared") + add_rules("csharp") + add_files("src/libalpha/*.cs") + +target("libbeta") + set_kind("shared") + add_rules("csharp") + add_deps("libalpha") + add_files("src/libbeta/*.cs") + +target("sample") + set_kind("binary") + add_rules("csharp") + add_deps("libbeta") + add_files("src/sample/*.cs") diff --git a/tests/projects/csharp/nuget_package/.gitignore b/tests/projects/csharp/nuget_package/.gitignore new file mode 100644 index 000000000..6b8d3af38 --- /dev/null +++ b/tests/projects/csharp/nuget_package/.gitignore @@ -0,0 +1,6 @@ +.xmake/ +build/ +bin/ +obj/ +vsxmake*/ +.vs/ diff --git a/tests/projects/csharp/nuget_package/src/Program.cs b/tests/projects/csharp/nuget_package/src/Program.cs new file mode 100644 index 000000000..518015e2a --- /dev/null +++ b/tests/projects/csharp/nuget_package/src/Program.cs @@ -0,0 +1,4 @@ +using Humanizer; + +Console.WriteLine(1234.ToWords()); + diff --git a/tests/projects/csharp/nuget_package/test.lua b/tests/projects/csharp/nuget_package/test.lua new file mode 100644 index 000000000..d909316b7 --- /dev/null +++ b/tests/projects/csharp/nuget_package/test.lua @@ -0,0 +1,13 @@ +import("detect.sdks.find_dotnet") + +function test_build(t) + if is_subhost("msys") then + return t:skip("csharp not supported on msys") + end + local dotnet = find_dotnet() + if dotnet and dotnet.sdkver then + t:build() + else + return t:skip("dotnet sdk not found") + end +end diff --git a/tests/projects/csharp/nuget_package/xmake.lua b/tests/projects/csharp/nuget_package/xmake.lua new file mode 100644 index 000000000..6ec474822 --- /dev/null +++ b/tests/projects/csharp/nuget_package/xmake.lua @@ -0,0 +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") + add_packages("nuget::Humanizer.Core") diff --git a/tests/projects/csharp/shared_library/.gitignore b/tests/projects/csharp/shared_library/.gitignore new file mode 100644 index 000000000..6b8d3af38 --- /dev/null +++ b/tests/projects/csharp/shared_library/.gitignore @@ -0,0 +1,6 @@ +.xmake/ +build/ +bin/ +obj/ +vsxmake*/ +.vs/ diff --git a/tests/projects/csharp/shared_library/src/app/Program.cs b/tests/projects/csharp/shared_library/src/app/Program.cs new file mode 100644 index 000000000..3cf7cb84a --- /dev/null +++ b/tests/projects/csharp/shared_library/src/app/Program.cs @@ -0,0 +1,4 @@ +using MyLib; + +Console.WriteLine(Greeter.Greet("xmake")); + diff --git a/tests/projects/csharp/shared_library/src/lib/Greeter.cs b/tests/projects/csharp/shared_library/src/lib/Greeter.cs new file mode 100644 index 000000000..cf895138a --- /dev/null +++ b/tests/projects/csharp/shared_library/src/lib/Greeter.cs @@ -0,0 +1,7 @@ +namespace MyLib; + +public static class Greeter { + public static string Greet(string name) { + return $"hello {name}!"; + } +} diff --git a/tests/projects/csharp/shared_library/test.lua b/tests/projects/csharp/shared_library/test.lua new file mode 100644 index 000000000..d909316b7 --- /dev/null +++ b/tests/projects/csharp/shared_library/test.lua @@ -0,0 +1,13 @@ +import("detect.sdks.find_dotnet") + +function test_build(t) + if is_subhost("msys") then + return t:skip("csharp not supported on msys") + end + local dotnet = find_dotnet() + if dotnet and dotnet.sdkver then + t:build() + else + return t:skip("dotnet sdk not found") + end +end diff --git a/tests/projects/csharp/shared_library/xmake.lua b/tests/projects/csharp/shared_library/xmake.lua new file mode 100644 index 000000000..f8549444e --- /dev/null +++ b/tests/projects/csharp/shared_library/xmake.lua @@ -0,0 +1,12 @@ +add_rules("mode.debug", "mode.release") + +target("mylib") + set_kind("shared") + add_rules("csharp") + add_files("src/lib/*.cs") + +target("app") + set_kind("binary") + add_rules("csharp") + add_deps("mylib") + add_files("src/app/*.cs") diff --git a/tests/projects/csharp/web_project/.gitignore b/tests/projects/csharp/web_project/.gitignore new file mode 100644 index 000000000..53e577525 --- /dev/null +++ b/tests/projects/csharp/web_project/.gitignore @@ -0,0 +1,7 @@ +.xmake/ +build/ +bin/ +obj/ +vsxmake*/ +.vs/ + diff --git a/tests/projects/csharp/web_project/src/Program.cs b/tests/projects/csharp/web_project/src/Program.cs new file mode 100644 index 000000000..7e432aaf0 --- /dev/null +++ b/tests/projects/csharp/web_project/src/Program.cs @@ -0,0 +1,8 @@ +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); + +app.MapGet("/", () => "hello web xmake!"); +app.MapGet("/health", () => Results.Ok(new { status = "ok" })); + +app.Run(); + diff --git a/tests/projects/csharp/web_project/test.lua b/tests/projects/csharp/web_project/test.lua new file mode 100644 index 000000000..d909316b7 --- /dev/null +++ b/tests/projects/csharp/web_project/test.lua @@ -0,0 +1,13 @@ +import("detect.sdks.find_dotnet") + +function test_build(t) + if is_subhost("msys") then + return t:skip("csharp not supported on msys") + end + local dotnet = find_dotnet() + if dotnet and dotnet.sdkver then + t:build() + else + return t:skip("dotnet sdk not found") + end +end diff --git a/tests/projects/csharp/web_project/xmake.lua b/tests/projects/csharp/web_project/xmake.lua new file mode 100644 index 000000000..56bcad79c --- /dev/null +++ b/tests/projects/csharp/web_project/xmake.lua @@ -0,0 +1,7 @@ +add_rules("mode.debug", "mode.release") + +target("webapp") + set_kind("binary") + add_rules("csharp") + add_values("csharp.sdk", "Microsoft.NET.Sdk.Web") + add_files("src/Program.cs") diff --git a/xmake/core/tool/compiler.lua b/xmake/core/tool/compiler.lua index dce7bf532..1afa7165e 100644 --- a/xmake/core/tool/compiler.lua +++ b/xmake/core/tool/compiler.lua @@ -203,73 +203,6 @@ function compiler.load(sourcekind, target) return instance end ---[[ --- build the source files (compile and link) -function compiler:build(sourcefiles, targetfile, opt) - opt = opt or {} - local target = opt.target or self:target() - if not opt.target and target then - opt = table.copy(opt) - opt.target = target - end - - -- get compile flags - local compflags = opt.compflags - if not compflags then - -- patch sourcefile to get flags of the given source file - if type(sourcefiles) == "string" then - opt.sourcefile = sourcefiles - end - compflags = self:compflags(opt) - end - - -- make flags - local flags = compflags - if target then - flags = table.join(flags, target:linkflags()) - end - - -- get target kind - local targetkind = opt.targetkind - if not targetkind and target and target.targetkind then - targetkind = target:kind() - end - return sandbox.load(self:_tool().build, self:_tool(), sourcefiles, targetkind or "binary", targetfile, flags, opt) -end - --- get the build arguments list (compile and link) -function compiler:buildargv(sourcefiles, targetfile, opt) - opt = opt or {} - local target = opt.target or self:target() - if not opt.target and target then - opt = table.copy(opt) - opt.target = target - end - - -- get compile flags - local compflags = opt.compflags - if not compflags then - -- patch sourcefile to get flags of the given source file - if type(sourcefiles) == "string" then - opt.sourcefile = sourcefiles - end - compflags = self:compflags(opt) - end - - -- make flags - local flags = compflags - if target then - flags = table.join(flags, target:linkflags()) - end - - -- get target kind - local targetkind = opt.targetkind - if not targetkind and target and target.targetkind then - targetkind = target:kind() - end - return self:_tool():buildargv(sourcefiles, targetkind or "binary", targetfile, flags, opt) -end]] - -- build the source files (compile and link) function compiler:build(sourcefiles, targetfile, opt) opt = opt or {} @@ -295,7 +228,7 @@ function compiler:build(sourcefiles, targetfile, opt) if not targetkind and opt.target and opt.target.targetkind then targetkind = opt.target:kind() end - return sandbox.load(self:_tool().build, self:_tool(), sourcefiles, targetkind or "binary", targetfile, flags) + return sandbox.load(self:_tool().build, self:_tool(), sourcefiles, targetkind or "binary", targetfile, flags, opt) end -- get the build arguments list (compile and link) @@ -323,7 +256,7 @@ function compiler:buildargv(sourcefiles, targetfile, opt) if not targetkind and opt.target and opt.target.targetkind then targetkind = opt.target:kind() end - return self:_tool():buildargv(sourcefiles, targetkind or "binary", targetfile, flags) + return self:_tool():buildargv(sourcefiles, targetkind or "binary", targetfile, flags, opt) end -- get the build command @@ -334,12 +267,6 @@ end -- compile the source files function compiler:compile(sourcefiles, objectfile, opt) opt = opt or {} - --[[ - local target = opt.target or self:target() - if not opt.target and target then - opt = table.copy(opt) - opt.target = target - end]] -- get compile flags local compflags = opt.compflags @@ -363,12 +290,6 @@ end -- get the compile arguments list function compiler:compargv(sourcefiles, objectfile, opt) opt = opt or {} - --[[ - local target = opt.target or self:target() - if not opt.target and target then - opt = table.copy(opt) - opt.target = target - end]] -- get compile flags local compflags = opt.compflags diff --git a/xmake/languages/csharp/load.lua b/xmake/languages/csharp/load.lua new file mode 100644 index 000000000..afff5c468 --- /dev/null +++ b/xmake/languages/csharp/load.lua @@ -0,0 +1,49 @@ +--!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 load.lua +-- + +function _get_apis() + local apis = {} + apis.values = { + -- target.add_xxx + "target.add_csflags" + , "target.add_ldflags" + , "target.add_shflags" + -- option.add_xxx + , "option.add_csflags" + , "option.add_ldflags" + , "option.add_shflags" + -- toolchain.add_xxx + , "toolchain.add_csflags" + , "toolchain.add_ldflags" + , "toolchain.add_shflags" + } + apis.paths = { + -- target.add_xxx + "target.add_linkdirs" + -- option.add_xxx + , "option.add_linkdirs" + } + return apis +end + +function main() + return {apis = _get_apis()} +end + diff --git a/xmake/languages/csharp/xmake.lua b/xmake/languages/csharp/xmake.lua new file mode 100644 index 000000000..9c2c0ce22 --- /dev/null +++ b/xmake/languages/csharp/xmake.lua @@ -0,0 +1,74 @@ +--!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 xmake.lua +-- + +language("csharp") + add_rules("csharp") + set_sourcekinds {cs = ".cs"} + set_sourceflags {cs = "csflags"} + set_targetkinds {binary = "cs", shared = "cs"} + set_targetflags {binary = "ldflags", shared = "shflags"} + set_langkinds {csharp = "cs"} + set_mixingkinds("cs") + + on_load("load") + + set_nameflags { + object = { + "target.symbols" + , "target.warnings" + , "target.optimize:check" + } + , binary = { + "config.linkdirs" + , "target.linkdirs" + , "config.links" + , "target.links" + , "config.syslinks" + , "target.syslinks" + } + , shared = { + "config.linkdirs" + , "target.linkdirs" + , "config.links" + , "target.links" + , "config.syslinks" + , "target.syslinks" + } + } + + set_menu { + config = + { + {category = "Cross Complation Configuration/Compiler Configuration" } + , {nil, "cs", "kv", nil, "The C# Compiler" } + + , {category = "Cross Complation Configuration/Linker Configuration" } + , {nil, "ld", "kv", nil, "The Linker" } + , {nil, "sh", "kv", nil, "The Shared Library Linker" } + + , {category = "Cross Complation Configuration/Compiler Flags Configuration" } + , {nil, "csflags", "kv", nil, "The C# Compiler Flags" } + + , {category = "Cross Complation Configuration/Linker Flags Configuration" } + , {nil, "ldflags", "kv", nil, "The Binary Linker Flags" } + , {nil, "shflags", "kv", nil, "The Shared Library Linker Flags" } + } + } + diff --git a/xmake/modules/core/tools/dotnet.lua b/xmake/modules/core/tools/dotnet.lua new file mode 100644 index 000000000..9a502b616 --- /dev/null +++ b/xmake/modules/core/tools/dotnet.lua @@ -0,0 +1,126 @@ +--!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 ruki +-- @file dotnet.lua +-- + +-- imports +import("core.base.option") +import("core.project.config") +import("core.project.project") +import("core.language.language") + +-- init it +function init(self) +end + +-- make the define flag +function nf_define(self, macro) + return {"-p:DefineConstants=" .. macro} +end + +-- make the optimize flag +function nf_optimize(self, level) + local maps = { + none = "-p:Optimize=false" + , fast = "-p:Optimize=true" + , faster = "-p:Optimize=true" + , fastest = "-p:Optimize=true" + , smallest = "-p:Optimize=true" + , aggressive = "-p:Optimize=true" + } + return maps[level] +end + +-- make the symbol flag +function nf_symbol(self, level) + local maps = { + debug = {"-p:DebugType=full", "-p:DebugSymbols=true"} + } + return maps[level] +end + +-- make the warning flag +function nf_warning(self, level) + local maps = { + none = "-p:WarningLevel=0" + , less = "-p:WarningLevel=1" + , more = "-p:WarningLevel=3" + , all = "-p:WarningLevel=5" + , allextra = "-p:WarningLevel=9999" + , error = {"-p:WarningLevel=5", "-p:TreatWarningsAsErrors=true"} + } + return maps[level] +end + +-- get the .csproj file from source files +function _get_csprojfile(opt) + local target = opt and opt.target + if target then + local csprojfile = target:data("csharp.csproj") + if csprojfile then + return csprojfile + end + end +end + +-- get dotnet verbosity level +function _get_verbosity() + if option.get("diagnosis") then + return "diagnostic" + end + return "quiet" +end + +-- get build configuration from mode +function _get_configuration() + local mode = config.mode() or "release" + if mode:lower() == "debug" then + return "Debug" + end + return "Release" +end + +-- make the build arguments list +function buildargv(self, sourcefiles, targetkind, targetfile, flags, opt) + local argv = {"build"} + + -- add .csproj file + local csprojfile = _get_csprojfile(opt) + if csprojfile then + table.insert(argv, csprojfile) + end + + -- add common options + table.join2(argv, {"--nologo", "--configuration", _get_configuration(), "--verbosity", _get_verbosity()}) + + -- add output directory + table.join2(argv, {"--output", path.directory(targetfile)}) + + -- add flags + if flags then + table.join2(argv, flags) + end + return self:program(), argv +end + +-- build the target file +function build(self, sourcefiles, targetkind, targetfile, flags, opt) + os.mkdir(path.directory(targetfile)) + local program, argv = buildargv(self, sourcefiles, targetkind, targetfile, flags, opt) + os.runv(program, argv, {envs = self:runenvs()}) +end diff --git a/xmake/modules/core/tools/dotnet/has_flags.lua b/xmake/modules/core/tools/dotnet/has_flags.lua new file mode 100644 index 000000000..eaffa7e88 --- /dev/null +++ b/xmake/modules/core/tools/dotnet/has_flags.lua @@ -0,0 +1,50 @@ +--!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 ruki +-- @file has_flags.lua +-- + +-- attempt to check it from known flags +function _check_from_knownargs(flags, opt) + local flag = flags[1] + -- dotnet MSBuild properties + if flag:startswith("-p:") or flag:startswith("/p:") then + return true + end + -- dotnet CLI options + if flag:startswith("--") then + return true + end + -- common csflags patterns + if flag:startswith("-") or flag:startswith("/") then + return true + end +end + +-- has_flags(flags)? +-- +-- @param opt the argument options, e.g. {toolname = "", program = "", programver = "", toolkind = "[cs|csld|cssh]"} +-- +-- @return true or false +-- +function main(flags, opt) + opt = opt or {} + if _check_from_knownargs(flags, opt) then + return true + end + return false +end diff --git a/xmake/modules/detect/sdks/find_dotnet.lua b/xmake/modules/detect/sdks/find_dotnet.lua index 2dfd2a916..f64acacd3 100644 --- a/xmake/modules/detect/sdks/find_dotnet.lua +++ b/xmake/modules/detect/sdks/find_dotnet.lua @@ -20,16 +20,73 @@ -- imports import("lib.detect.find_file") +import("lib.detect.find_tool") import("core.tool.toolchain") import("core.base.option") import("core.base.global") import("core.project.config") import("core.cache.detectcache") --- find dotnet directory -function _find_sdkdir(sdkdir) +-- find dotnet sdk info from dotnet cli +function _find_dotnet_cli(sdkdir) - -- get sdk directory from vcvars + -- find dotnet program + local paths = {} + if sdkdir then + table.insert(paths, path.join(sdkdir, "bin")) + table.insert(paths, sdkdir) + end + local dotnet = find_tool("dotnet", {version = true, paths = #paths > 0 and paths or nil}) + if not dotnet then + return nil + end + + local bindir = path.directory(dotnet.program) + local result = {bindir = bindir, version = dotnet.version} + + -- get sdk list, e.g. "8.0.100 [/usr/share/dotnet/sdk]" + local sdklist = try { function () return os.iorunv(dotnet.program, {"--list-sdks"}) end } + if sdklist then + local sdks = {} + for _, line in ipairs(sdklist:split("\n", {plain = true})) do + line = line:trim() + local ver, dir = line:match("^(%S+)%s+%[(.-)%]") + if ver and dir then + table.insert(sdks, {version = ver, directory = path.join(dir, ver)}) + end + end + if #sdks > 0 then + result.sdks = sdks + result.sdkdir = sdks[#sdks].directory + result.sdkver = sdks[#sdks].version + end + end + + -- set sdkdir from bindir if not found from sdk list + if not result.sdkdir then + result.sdkdir = sdkdir or path.directory(bindir) + end + + -- get runtime list, e.g. "Microsoft.NETCore.App 8.0.0 [/usr/share/dotnet/shared/Microsoft.NETCore.App]" + local runtimelist = try { function () return os.iorunv(dotnet.program, {"--list-runtimes"}) end } + if runtimelist then + local runtimes = {} + for _, line in ipairs(runtimelist:split("\n", {plain = true})) do + line = line:trim() + local name, ver = line:match("^(%S+)%s+(%S+)%s+%[") + if name and ver then + table.insert(runtimes, {name = name, version = ver}) + end + end + if #runtimes > 0 then + result.runtimes = runtimes + end + end + return result +end + +-- find .NET Framework SDK directory from MSVC (Windows only) +function _find_netfxsdk(sdkdir) if not sdkdir then local msvc = toolchain.load("msvc") if msvc and msvc:check() then @@ -42,55 +99,40 @@ function _find_sdkdir(sdkdir) end end end - return sdkdir -end - --- find dotnet toolchains -function _find_dotnet(sdkdir, sdkver) - - -- find dotnet directory - sdkdir = _find_sdkdir(sdkdir) if not sdkdir or not os.isdir(sdkdir) then return nil end -- get sdk version - if not sdkver then - local vers = {} - for _, dir in ipairs(os.dirs(path.join(sdkdir, "*", "Include"))) do - table.insert(vers, path.filename(path.directory(dir))) - end - for _, ver in ipairs(vers) do - if os.isdir(path.join(sdkdir, ver, "Lib", "um")) and os.isdir(path.join(sdkdir, ver, "Include", "um")) then - sdkver = ver - break - end + local sdkver + local vers = {} + for _, dir in ipairs(os.dirs(path.join(sdkdir, "*", "Include"))) do + table.insert(vers, path.filename(path.directory(dir))) + end + for _, ver in ipairs(vers) do + if os.isdir(path.join(sdkdir, ver, "Lib", "um")) and os.isdir(path.join(sdkdir, ver, "Include", "um")) then + sdkver = ver + break end end if not sdkver then return nil end - - -- get the lib directory - local libdir = path.join(sdkdir, sdkver, "Lib") - - -- get the include directory - local includedir = path.join(sdkdir, sdkver, "Include") - - -- get toolchains - return {sdkdir = sdkdir, libdir = libdir, includedir = includedir, sdkver = sdkver} + return {sdkdir = sdkdir, sdkver = sdkver, + libdir = path.join(sdkdir, sdkver, "Lib"), + includedir = path.join(sdkdir, sdkver, "Include")} end --- find dotnet toolchains +-- find dotnet sdk -- -- @param sdkdir the dotnet directory --- @param opt the argument options, e.g. {verbose = true, force = false, version = "5.9.1"} +-- @param opt the argument options, e.g. {verbose = true, force = false} -- --- @return the dotnet toolchains. e.g. {sdkver = ..., sdkdir = ..., bindir = .., libdir = ..., includedir = ..., .. } +-- @return the dotnet sdk info, e.g. {program = ..., version = ..., sdkver = ..., sdkdir = ..., sdks = {...}, runtimes = {...}} -- -- @code -- --- local toolchains = find_dotnet("~/dotnet") +-- local dotnet = find_dotnet() -- -- @endcode -- @@ -100,30 +142,51 @@ function main(sdkdir, opt) opt = opt or {} -- attempt to load cache first - local key = "detect.sdks.find_dotnet." .. (sdkdir or "") + local key = "detect.sdks.find_dotnet" local cacheinfo = detectcache:get(key) or {} - if not opt.force and cacheinfo.dotnet then + if not opt.force and cacheinfo.dotnet and cacheinfo.dotnet.sdkdir and os.isdir(cacheinfo.dotnet.sdkdir) then return cacheinfo.dotnet end - -- find dotnet - local dotnet = _find_dotnet(sdkdir or config.get("dotnet") or global.get("dotnet"), opt.version or config.get("dotnet_sdkver")) + -- find dotnet cli sdk + local dotnet = _find_dotnet_cli(sdkdir or config.get("dotnet") or global.get("dotnet")) + + -- find .NET Framework SDK on Windows + if is_host("windows") then + local netfxsdk = _find_netfxsdk(sdkdir or config.get("dotnet") or global.get("dotnet")) + if netfxsdk then + if dotnet then + dotnet.netfxsdk = netfxsdk + else + dotnet = netfxsdk + end + end + end + if dotnet then -- save to config - config.set("dotnet", dotnet.sdkdir, {force = true, readonly = true}) - config.set("dotnet_sdkver", dotnet.sdkver, {force = true, readonly = true}) + if dotnet.sdkdir then + config.set("dotnet", dotnet.sdkdir, {force = true, readonly = true}) + end + if dotnet.sdkver then + config.set("dotnet_sdkver", dotnet.sdkver, {force = true, readonly = true}) + end -- trace if opt.verbose or option.get("verbose") then - cprint("checking for .Net SDK directory ... ${color.success}%s", dotnet.sdkdir) - cprint("checking for .Net SDK version ... ${color.success}%s", dotnet.sdkver) + if dotnet.version then + cprint("checking for .NET SDK version ... ${color.success}%s", dotnet.version) + end + if dotnet.sdkdir then + cprint("checking for .NET SDK directory ... ${color.success}%s", dotnet.sdkdir) + end end else -- trace if opt.verbose or option.get("verbose") then - cprint("checking for .Net SDK directory ... ${color.nothing}${text.nothing}") + cprint("checking for .NET SDK directory ... ${color.nothing}${text.nothing}") end end diff --git a/xmake/modules/detect/tools/find_dotnet.lua b/xmake/modules/detect/tools/find_dotnet.lua new file mode 100644 index 000000000..ac17979ec --- /dev/null +++ b/xmake/modules/detect/tools/find_dotnet.lua @@ -0,0 +1,53 @@ +--!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 ruki +-- @file find_dotnet.lua +-- + +-- imports +import("lib.detect.find_program") +import("lib.detect.find_programver") + +-- find dotnet +-- +-- @param opt the argument options, e.g. {version = true} +-- +-- @return program, version +-- +-- @code +-- +-- local dotnet = find_dotnet() +-- +-- @endcode +-- +function main(opt) + + -- init options + opt = opt or {} + opt.check = opt.check or "--version" + opt.command = opt.command or "--version" + + -- find program + local program = find_program(opt.program or "dotnet", opt) + + -- find program version + local version = nil + if program and opt and opt.version then + version = find_programver(program, opt) + end + return program, version +end diff --git a/xmake/modules/package/manager/nuget/find_package.lua b/xmake/modules/package/manager/nuget/find_package.lua index a206eb301..4a27eed19 100644 --- a/xmake/modules/package/manager/nuget/find_package.lua +++ b/xmake/modules/package/manager/nuget/find_package.lua @@ -28,6 +28,82 @@ import("core.project.config") import("core.project.target") import("private.utils.toolchain", {alias = "toolchain_utils"}) +function _find_target_root(targets, name) + local namelower = name:lower() + for targetname in pairs(targets or {}) do + local targetpkg = targetname:match("^([^/]+)") or targetname + local targetpkglower = targetpkg:lower() + local targetnamelower = targetname:lower() + local normalized_targetpkg = targetpkglower:gsub("[._%-]", "") + local normalized_name = namelower:gsub("[._%-]", "") + if targetnamelower == namelower + or targetnamelower:startswith(namelower .. "/") + or targetpkglower == namelower + or normalized_targetpkg == normalized_name then + return targetname + end + end +end + +function _find_library_root(libraries, name) + local namelower = name:lower() + for libraryname in pairs(libraries or {}) do + local librarypkg = libraryname:match("^([^/]+)") or libraryname + local librarypkglower = librarypkg:lower() + local librarynamelower = libraryname:lower() + local normalized_librarypkg = librarypkglower:gsub("[._%-]", "") + local normalized_name = namelower:gsub("[._%-]", "") + if librarynamelower == namelower + or librarynamelower:startswith(namelower .. "/") + or librarypkglower == namelower + or normalized_librarypkg == normalized_name then + return libraryname + end + end +end + +function _find_version_in_project_dependencies(manifest, name) + local namelower = name:lower() + local normalized_name = namelower:gsub("[._%-]", "") + if manifest.project and manifest.project.frameworks then + for _, framework in pairs(manifest.project.frameworks) do + local dependencies = framework.dependencies or {} + for depname, depver in pairs(dependencies) do + local deplower = depname:lower() + local normalized_depname = deplower:gsub("[._%-]", "") + if deplower == namelower or normalized_depname == normalized_name then + return tostring(depver) + end + end + end + end +end + +function _get_packagesdir_from_manifest(manifest) + if manifest.project and manifest.project.restore and manifest.project.restore.packagesPath then + return manifest.project.restore.packagesPath + end + if manifest.packageFolders then + for folder in pairs(manifest.packageFolders) do + return folder + end + end + local packagesdir = os.getenv("NUGET_PACKAGES") + if packagesdir and #packagesdir > 0 then + return packagesdir + end + local homedir = os.homedir and os.homedir() + if homedir and #homedir > 0 then + return path.join(homedir, ".nuget", "packages") + end +end + +local function _extract_version_from_root(rootname) + if rootname then + return rootname:match("/(.+)$") + end +end + -- check if pattern matches as a complete path component -- e.g. "x86" should match "/x86/" but not "/x86_64/" function _match_path_component(file_lower, pattern) @@ -86,7 +162,7 @@ function _match_libfile(file, libarch, toolset, libmode, runtime) if file_lower:find(libmode:lower(), 1, true) then score = score + 2 end - if file_lower:find(runtime:lower(), 1, true) then + if runtime and file_lower:find(runtime:lower(), 1, true) then score = score + 1 end return score @@ -105,9 +181,10 @@ function _find_package(name, result, opt) MTd = "MultiThreadedDebug", MD = "MultiThreadedDLL", MDd = "MultiThreadedDebugDLL"} + local runtime = runtimes[configs.runtimes] local installdir = path.join(opt.packagesdir, name) - local runtime = assert(runtimes[configs.runtimes], "unknown runtimes %s", configs.runtimes) - local toolset = toolchain_utils.get_vs_toolset_ver(toolchain.load("msvc", {plat = plat, arch = arch}):config("vs_toolset") or config.get("vs_toolset")) + local msvc = toolchain.load("msvc", {plat = plat, arch = arch}) + local toolset = msvc and toolchain_utils.get_vs_toolset_ver(msvc:config("vs_toolset") or config.get("vs_toolset")) or nil local libarch = libarchs[arch] or "x64" local libmode = configs.debug and "Debug" or "Release" for _, file in ipairs(libinfo.files) do @@ -169,6 +246,22 @@ function _find_package(name, result, opt) end end +function _cleanup_result(result, version) + result._libscores = nil + if version and not result.version then + result.version = version + end + if result.links or result.includedirs then + if result.includedirs then + result.includedirs = table.unique(result.includedirs) + end + if result.linkdirs then + result.linkdirs = table.unique(result.linkdirs) + end + end + return result +end + -- find package from the nuget package manager -- -- @param name the package name, e.g. zlib, pcre @@ -185,46 +278,57 @@ function main(name, opt) return end local manifest = json.loadfile(manifestfile) - local targets - for k, v in pairs(manifest.targets) do - targets = v - break + local packagesdir = _get_packagesdir_from_manifest(manifest) + if not manifest.libraries then + return end - local target_root - if targets then - for k, v in pairs(targets) do - if k:startswith(name) then - target_root = k - break + + if manifest.targets and packagesdir then + for _, targets in pairs(manifest.targets) do + local target_root = _find_target_root(targets, name) + if target_root then + local metainfo = {} + metainfo.plat = opt.plat + metainfo.arch = opt.arch + metainfo.mode = opt.mode + metainfo.configs = opt.configs + metainfo.targets = targets + metainfo.libraries = manifest.libraries + metainfo.packagesdir = packagesdir + local result = {} + _find_package(target_root, result, metainfo) + return _cleanup_result(result, _extract_version_from_root(target_root)) end end end - local metainfo = {} - metainfo.plat = opt.plat - metainfo.arch = opt.arch - metainfo.mode = opt.mode - metainfo.configs = opt.configs - metainfo.targets = targets - metainfo.libraries = manifest.libraries - if manifest.project and manifest.project.restore then - metainfo.packagesdir = manifest.project.restore.packagesPath - end - if target_root and metainfo.targets and metainfo.libraries and metainfo.packagesdir then - local result = {} - _find_package(target_root, result, metainfo) - -- clean up internal scoring data - result._libscores = nil - if result.links or result.includedirs then - -- deduplicate paths - if result.includedirs then - result.includedirs = table.unique(result.includedirs) - end - if result.linkdirs then - result.linkdirs = table.unique(result.linkdirs) - end - return result + + local library_root = _find_library_root(manifest.libraries, name) + if library_root then + if packagesdir then + local metainfo = {} + metainfo.plat = opt.plat + metainfo.arch = opt.arch + metainfo.mode = opt.mode + metainfo.configs = opt.configs + metainfo.targets = {} + metainfo.libraries = manifest.libraries + metainfo.packagesdir = packagesdir + local result = {} + _find_package(library_root, result, metainfo) + return _cleanup_result(result, _extract_version_from_root(library_root)) end + -- package exists in manifest but package root cannot be determined, + -- treat managed-only package as found. + return {version = _extract_version_from_root(library_root)} end -end - + -- fallback for managed package references if target/library keys are not aligned + -- with the requested package name format. + local depver = _find_version_in_project_dependencies(manifest, name) + if depver then + return {version = depver} + end + if opt.require_version then + return {version = tostring(opt.require_version)} + end +end diff --git a/xmake/modules/target/action/install/main.lua b/xmake/modules/target/action/install/main.lua index 2e92122e8..466ebe551 100644 --- a/xmake/modules/target/action/install/main.lua +++ b/xmake/modules/target/action/install/main.lua @@ -49,8 +49,6 @@ function _get_target_includedir(target, opt) return path.join(opt.installdir, opt.includedir) end - - -- copy file with symlinks function _copy_file_with_symlinks(srcfile, outputdir) if os.islink(srcfile) then diff --git a/xmake/platforms/bsd/xmake.lua b/xmake/platforms/bsd/xmake.lua index 3c34e2420..98143d019 100644 --- a/xmake/platforms/bsd/xmake.lua +++ b/xmake/platforms/bsd/xmake.lua @@ -30,7 +30,7 @@ platform("bsd") set_installdir("/usr/local") - set_toolchains("envs", "gcc", "clang", "yasm", "nasm", "fasm", "cuda", "go", "rust", "gfortran", "zig") + set_toolchains("envs", "gcc", "clang", "yasm", "nasm", "fasm", "cuda", "go", "rust", "gfortran", "zig", "dotnet") set_menu { config = diff --git a/xmake/platforms/linux/xmake.lua b/xmake/platforms/linux/xmake.lua index 8b0232241..b411dc4d3 100644 --- a/xmake/platforms/linux/xmake.lua +++ b/xmake/platforms/linux/xmake.lua @@ -36,7 +36,7 @@ platform("linux") -- -- TODO Perhaps we should handle it better, or remove the cross toolchain. set_toolchains("envs", "gcc", "clang", - "cross", "yasm", "nasm", "fasm", "cuda", "go", "rust", "swift", "gfortran", "zig", "fpc", "nim") + "cross", "yasm", "nasm", "fasm", "cuda", "go", "rust", "swift", "gfortran", "zig", "fpc", "nim", "dotnet") set_menu { config = diff --git a/xmake/platforms/macosx/xmake.lua b/xmake/platforms/macosx/xmake.lua index 348c61f55..869d83843 100644 --- a/xmake/platforms/macosx/xmake.lua +++ b/xmake/platforms/macosx/xmake.lua @@ -29,7 +29,7 @@ platform("macosx") set_formats("symbol", "$(name).dSYM") set_installdir("/usr/local") - set_toolchains("envs", "xcode", "clang", "gcc", "yasm", "nasm", "cuda", "rust", "go", "gfortran", "zig", "fpc", "nim") + set_toolchains("envs", "xcode", "clang", "gcc", "yasm", "nasm", "cuda", "rust", "go", "gfortran", "zig", "fpc", "nim", "dotnet") set_menu { config = diff --git a/xmake/platforms/windows/xmake.lua b/xmake/platforms/windows/xmake.lua index 4d8335171..4bb307c7e 100644 --- a/xmake/platforms/windows/xmake.lua +++ b/xmake/platforms/windows/xmake.lua @@ -29,7 +29,7 @@ platform("windows") set_formats("binary", "$(name).exe") set_formats("symbol", "$(name).pdb") - set_toolchains("msvc", "clang", "yasm", "nasm", "cuda", "rust", "swift", "go", "gfortran", "zig", "fpc", "nim") + set_toolchains("msvc", "clang", "yasm", "nasm", "cuda", "rust", "swift", "go", "gfortran", "zig", "fpc", "nim", "dotnet") set_menu { config = diff --git a/xmake/repository/templates/csharp/console/src/Program.cs b/xmake/repository/templates/csharp/console/src/Program.cs new file mode 100644 index 000000000..5f75209cb --- /dev/null +++ b/xmake/repository/templates/csharp/console/src/Program.cs @@ -0,0 +1,9 @@ +using System; + +namespace ${TARGET_NAME}; + +class Program { + static void Main(string[] args) { + Console.WriteLine("Hello, xmake!"); + } +} diff --git a/xmake/repository/templates/csharp/console/xmake.lua b/xmake/repository/templates/csharp/console/xmake.lua new file mode 100644 index 000000000..d19ce0fcd --- /dev/null +++ b/xmake/repository/templates/csharp/console/xmake.lua @@ -0,0 +1,7 @@ +add_rules("mode.debug", "mode.release") + +target("${TARGET_NAME}") + set_kind("binary") + add_files("src/*.cs") + +${FAQ} diff --git a/xmake/repository/templates/csharp/shared/src/foo.cs b/xmake/repository/templates/csharp/shared/src/foo.cs new file mode 100644 index 000000000..bba30d999 --- /dev/null +++ b/xmake/repository/templates/csharp/shared/src/foo.cs @@ -0,0 +1,7 @@ +namespace Foo; + +public class Foo { + public static int Add(int a, int b) { + return a + b; + } +} diff --git a/xmake/repository/templates/csharp/shared/src/main.cs b/xmake/repository/templates/csharp/shared/src/main.cs new file mode 100644 index 000000000..c2e4eb2fd --- /dev/null +++ b/xmake/repository/templates/csharp/shared/src/main.cs @@ -0,0 +1,7 @@ +using System; + +class Program { + static void Main(string[] args) { + Console.WriteLine("add(1, 2) = {0}", Foo.Foo.Add(1, 2)); + } +} diff --git a/xmake/repository/templates/csharp/shared/xmake.lua b/xmake/repository/templates/csharp/shared/xmake.lua new file mode 100644 index 000000000..256ed0f65 --- /dev/null +++ b/xmake/repository/templates/csharp/shared/xmake.lua @@ -0,0 +1,10 @@ +target("foo") + set_kind("shared") + add_files("src/foo.cs") + +target("${TARGET_NAME}_demo") + set_kind("binary") + add_deps("foo") + add_files("src/main.cs") + +${FAQ} diff --git a/xmake/repository/templates/csharp/static/src/foo.cs b/xmake/repository/templates/csharp/static/src/foo.cs new file mode 100644 index 000000000..bba30d999 --- /dev/null +++ b/xmake/repository/templates/csharp/static/src/foo.cs @@ -0,0 +1,7 @@ +namespace Foo; + +public class Foo { + public static int Add(int a, int b) { + return a + b; + } +} diff --git a/xmake/repository/templates/csharp/static/src/main.cs b/xmake/repository/templates/csharp/static/src/main.cs new file mode 100644 index 000000000..c2e4eb2fd --- /dev/null +++ b/xmake/repository/templates/csharp/static/src/main.cs @@ -0,0 +1,7 @@ +using System; + +class Program { + static void Main(string[] args) { + Console.WriteLine("add(1, 2) = {0}", Foo.Foo.Add(1, 2)); + } +} diff --git a/xmake/repository/templates/csharp/static/xmake.lua b/xmake/repository/templates/csharp/static/xmake.lua new file mode 100644 index 000000000..51bce92a0 --- /dev/null +++ b/xmake/repository/templates/csharp/static/xmake.lua @@ -0,0 +1,10 @@ +target("foo") + set_kind("static") + add_files("src/foo.cs") + +target("${TARGET_NAME}_demo") + set_kind("binary") + add_deps("foo") + add_files("src/main.cs") + +${FAQ} diff --git a/xmake/rules/csharp/build.lua b/xmake/rules/csharp/build.lua new file mode 100644 index 000000000..9bc6f2e76 --- /dev/null +++ b/xmake/rules/csharp/build.lua @@ -0,0 +1,86 @@ +--!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 ruki +-- @file target.lua +-- + +-- imports +import("core.base.option") +import("core.tool.compiler") +import("core.project.depend") +import("utils.progress") + +-- build the source files +function build_sourcefiles(target, sourcebatch, opt) + + -- get the target file + local targetfile = target:targetfile() + + -- get source files and kind + local sourcefiles = sourcebatch.sourcefiles + local sourcekind = sourcebatch.sourcekind + local csprojfile = target:data("csharp.csproj") + + -- get depend file + local dependfile = target:dependfile(targetfile) + + -- load compiler + local compinst = compiler.load(sourcekind, {target = target}) + + -- get compile flags + local compflags = compinst:compflags({target = target}) + + -- load dependent info + local dependinfo = option.get("rebuild") and {} or (depend.load(dependfile) or {}) + + -- need build this target? + local depvalues = {compinst:program(), compflags} + if not depend.is_changed(dependinfo, {lastmtime = os.mtime(targetfile), values = depvalues}) then + return + end + + -- trace progress info + progress.show(opt.progress, "${color.build.target}linking.$(mode) %s", path.filename(targetfile)) + + -- trace verbose info + vprint(compinst:buildcmd(sourcefiles, targetfile, {target = target, compflags = compflags})) + + -- flush io buffer to update progress info + io.flush() + + -- build it + dependinfo.files = {} + assert(compinst:build(sourcefiles, targetfile, {target = target, dependinfo = dependinfo, compflags = compflags})) + + -- update files and values to the dependent file + dependinfo.values = depvalues + table.join2(dependinfo.files, sourcefiles, csprojfile) + depend.save(dependinfo, dependfile) +end + +-- build target +function main(target, opt) + + -- @note only support one source kind! + local sourcebatches = target:sourcebatches() + if sourcebatches then + local sourcebatch = sourcebatches["csharp.build"] + if sourcebatch then + build_sourcefiles(target, sourcebatch, opt) + end + end +end diff --git a/xmake/rules/csharp/config.lua b/xmake/rules/csharp/config.lua new file mode 100644 index 000000000..3f6b71844 --- /dev/null +++ b/xmake/rules/csharp/config.lua @@ -0,0 +1,47 @@ +--!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 ruki +-- @file config.lua +-- + +-- imports +import("core.project.depend") +import("generator.csproj", {rootdir = os.scriptdir(), alias = "generate_csproj"}) + +function main(target) + + -- compute csproj path + local csprojfile = path.join(target:autogendir(), "rules", "csharp", target:name() .. ".csproj") + local dependfile = target:dependfile(csprojfile) + + -- collect source files and dep csproj paths as depend values + local sourcefiles = target:sourcefiles() + local depcsproj = {} + for _, dep in ipairs(target:orderdeps()) do + local depcsproj_path = dep:data("csharp.csproj") + if depcsproj_path then + table.insert(depcsproj, depcsproj_path) + end + end + + -- generate csproj incrementally + depend.on_changed(function () + generate_csproj(target, csprojfile) + end, {dependfile = dependfile, files = sourcefiles, values = depcsproj}) + + target:data_set("csharp.csproj", csprojfile) +end diff --git a/xmake/rules/csharp/generator/csproj.lua b/xmake/rules/csharp/generator/csproj.lua new file mode 100644 index 000000000..aa4e0434c --- /dev/null +++ b/xmake/rules/csharp/generator/csproj.lua @@ -0,0 +1,257 @@ +--!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.lua +-- + +-- imports +import("properties") +import("itemgroups") + +-- escape special xml characters +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 + +-- format key-value pairs as xml attributes string, e.g. ` Sdk="Microsoft.NET.Sdk"` +function _format_attributes(attrs) + if type(attrs) ~= "table" then + return "" + end + local keys = {} + for key, value in table.orderpairs(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 + +-- get single csharp value from target:values(), with default fallback +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 + +-- resolve a registry entry value, supports custom resolve function, list type and single value +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 + +-- collect <Project> element attributes, e.g. Sdk="Microsoft.NET.Sdk" +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 + +-- collect <PropertyGroup> entries from registered csharp.* properties +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 + +-- normalize item entry to {xml, attrs, value} format +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 table.orderpairs(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 + +-- collect <ItemGroup> entries (Compile, ProjectReference, PackageReference, ..) +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 + +-- collect custom properties from target:values("csharp.properties") +-- value format: "Name=Value", e.g. set_values("csharp.properties", "MyProp=value") +function _collect_custom_property_entries(target) + local entries = {} + for _, item in ipairs(table.wrap(target:values("csharp.properties"))) do + local name, value = tostring(item):match("^%s*([^=]+)%s*=(.*)$") + if name and #value > 0 then + table.insert(entries, {xml = name:trim(), value = value}) + end + end + return entries +end + +-- render <PropertyGroup> section to file +function _render_property_group(file, entries) + if #entries == 0 then + return + end + file:print(" <PropertyGroup>") + for _, entry in ipairs(entries) do + file:print(" <%s>%s</%s>", entry.xml, _xml_escape(entry.value), entry.xml) + end + file:print(" </PropertyGroup>") +end + +-- render <ItemGroup> sections to file +function _render_item_groups(file, item_groups) + for _, group in ipairs(item_groups) do + if #group.items > 0 then + file:print(" <ItemGroup>") + for _, item in ipairs(group.items) do + local attrs = _format_attributes(item.attrs) + if item.value ~= nil and item.value ~= "" then + file:print(" <%s%s>%s</%s>", item.xml, attrs, _xml_escape(item.value), item.xml) + else + file:print(" <%s%s />", item.xml, attrs) + end + end + file:print(" </ItemGroup>") + end + end +end + +-- generate .csproj file for the target, write to tmpfile first then copy if different +function main(target, csprojfile, opt) + opt = opt or {} + + local csprojdir = path.directory(csprojfile) + local context = { + target = target, + csprojfile = csprojfile, + csprojdir = csprojdir, + opt = opt + } + + -- collect project properties + local property_registry_entries = properties() + local item_registry_entries = 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) + + -- generate csproj + local tmpfile = os.tmpfile() .. ".csproj" + local file = io.open(tmpfile, "w") + file:print("<Project%s>", _format_attributes(project_attributes)) + _render_property_group(file, property_entries) + _render_item_groups(file, item_groups) + file:print("</Project>") + file:close() + + os.mkdir(csprojdir) + os.cp(tmpfile, csprojfile, {copy_if_different = true}) + os.rm(tmpfile) +end diff --git a/xmake/rules/csharp/generator/itemgroups.lua b/xmake/rules/csharp/generator/itemgroups.lua new file mode 100644 index 000000000..2c0528665 --- /dev/null +++ b/xmake/rules/csharp/generator/itemgroups.lua @@ -0,0 +1,163 @@ +--!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 itemgroups.lua +-- + +-- normalize path to relative and use forward slashes +function _normalize_relative(fromdir, targetpath) + local relpath = path.relative(targetpath, fromdir) or targetpath + return path.unix(relpath) +end + +-- collect .cs source files as relative paths to csprojdir +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 + +-- collect ProjectReference paths from dependency targets +function _collect_project_references(context) + local references = {} + for _, dep in ipairs(context.target:orderdeps()) do + local depcsproj = dep:data("csharp.csproj") + if depcsproj then + table.insert(references, _normalize_relative(context.csprojdir, depcsproj)) + end + end + table.sort(references) + return table.unique(references) +end + +-- extract nuget package name and version from package require string +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 + +-- collect PackageReference entries from nuget packages +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 table.orderpairs(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 + +-- register all item group entries (Compile, ProjectReference, PackageReference) +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/generator/properties.lua b/xmake/rules/csharp/generator/properties.lua new file mode 100644 index 000000000..15702eae4 --- /dev/null +++ b/xmake/rules/csharp/generator/properties.lua @@ -0,0 +1,174 @@ +--!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 +-- + +-- check if target has multi-target frameworks set +function _has_target_frameworks(context) + return #table.wrap(context.target:values("csharp.target_frameworks")) > 0 +end + +-- get the first element if value is a table +function _first(value) + if type(value) == "table" then + return value[1] + end + return value +end + +-- get single value from target:values() +function _get_target_value(target, name) + return _first(target:values(name)) +end + +-- get default target framework from dotnet toolchain sdk version, e.g. "net8.0" +function _get_default_target_framework(context) + local major + local toolchain = context.target:toolchain("dotnet") + if toolchain then + local sdkver = toolchain:config("sdkver") + if sdkver then + major = tonumber(tostring(sdkver):match("^(%d+)")) + end + end + return "net" .. tostring(major or 8) .. ".0" +end + +-- resolve target framework from user config or auto-detect from dotnet sdk +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 + +-- resolve assembly name from target basename +function _resolve_assembly_name(context) + local basename = context.target:basename() + if basename ~= nil and #tostring(basename) > 0 then + return basename + end +end + + +-- register a single-value csharp.* property entry +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 + +-- register a list-value csharp.* property entry (semicolon-joined) +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 + +-- register all csharp property and project attribute entries for csproj generation +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_property(register, "prefer_32bit", "Prefer32Bit") + _register_property(register, "allow_unsafe_blocks", "AllowUnsafeBlocks") + _register_property(register, "check_for_overflow_underflow", "CheckForOverflowUnderflow") + _register_property(register, "analysis_level", "AnalysisLevel") + _register_property(register, "enable_net_analyzers", "EnableNETAnalyzers") + _register_property(register, "enforce_code_style_in_build", "EnforceCodeStyleInBuild") + _register_list_property(register, "warnings_as_errors", "WarningsAsErrors") + _register_list_property(register, "warnings_not_as_errors", "WarningsNotAsErrors") + _register_property(register, "error_log", "ErrorLog") + _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/install.lua b/xmake/rules/csharp/install.lua new file mode 100644 index 000000000..03e2cd24d --- /dev/null +++ b/xmake/rules/csharp/install.lua @@ -0,0 +1,80 @@ +--!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 ruki +-- @file install.lua +-- + +-- imports +import("core.project.config") + +-- get dotnet program +function _get_dotnet(target) + return target:tool("cs") or "dotnet" +end + +-- get build configuration from mode +function _get_configuration() + local mode = config.mode() or "release" + if mode:lower() == "debug" then + return "Debug" + end + return "Release" +end + +-- get output directory based on target kind +-- on windows, shared libraries (dll) should also go to bindir +function _get_outputdir(target) + if target:is_binary() or (target:is_shared() and target:is_plat("windows", "mingw")) then + return target:bindir() + else + return target:libdir() + end +end + +-- install csharp target using dotnet publish +function main(target) + local installdir = target:installdir() + if not installdir then + return + end + + -- get output directory based on target kind + local outputdir = _get_outputdir(target) + if not outputdir then + return + end + + -- run dotnet publish + local csprojfile = target:data("csharp.csproj") + local argv = {"publish"} + if csprojfile then + table.insert(argv, csprojfile) + end + table.join2(argv, {"--nologo", + "--configuration", _get_configuration(), + "--output", outputdir}) + local dotnet = _get_dotnet(target) + os.vrunv(dotnet, argv) + + -- install extra files (add_installfiles) + local srcfiles, dstfiles = target:installfiles(installdir) + if srcfiles and dstfiles then + for idx, srcfile in ipairs(srcfiles) do + os.vcp(srcfile, dstfiles[idx]) + end + end +end diff --git a/xmake/rules/csharp/installcmd.lua b/xmake/rules/csharp/installcmd.lua new file mode 100644 index 000000000..25de92989 --- /dev/null +++ b/xmake/rules/csharp/installcmd.lua @@ -0,0 +1,83 @@ +--!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 ruki +-- @file installcmd.lua +-- + +-- imports +import("core.project.config") + +-- get dotnet program +function _get_dotnet(target) + return target:tool("cs") or "dotnet" +end + +-- get build configuration from mode +function _get_configuration() + local mode = config.mode() or "release" + if mode:lower() == "debug" then + return "Debug" + end + return "Release" +end + +-- install csharp target for xpack using dotnet publish +function main(target, batchcmds, opt) + local package = opt.package + if not package then + return + end + + local installdir = package:installdir() + if not installdir then + return + end + + -- get output directory based on target kind + -- on windows, shared libraries (dll) should also go to bindir + local outputdir + if target:is_binary() or (target:is_shared() and target:is_plat("windows", "mingw")) then + outputdir = package:installdir("bin") + else + outputdir = package:installdir("lib") + end + + -- run dotnet publish to a temporary publish directory, then copy to install directory + local publishdir = path.join(target:autogendir(), "rules", "csharp", "publish") + local csprojfile = target:data("csharp.csproj") + local argv = {"publish"} + if csprojfile then + table.insert(argv, csprojfile) + end + table.join2(argv, {"--nologo", + "--configuration", _get_configuration(), + "--output", publishdir}) + local dotnet = _get_dotnet(target) + batchcmds:vrunv(dotnet, argv) + + -- copy published files to output directory + batchcmds:mkdir(outputdir) + batchcmds:cp(path.join(publishdir, "**"), outputdir, {rootdir = publishdir}) + + -- install extra files (add_installfiles) + local srcfiles, dstfiles = target:installfiles(installdir) + if srcfiles and dstfiles then + for idx, srcfile in ipairs(srcfiles) do + batchcmds:cp(srcfile, dstfiles[idx]) + end + end +end diff --git a/xmake/rules/csharp/xmake.lua b/xmake/rules/csharp/xmake.lua new file mode 100644 index 000000000..9a0a6e43b --- /dev/null +++ b/xmake/rules/csharp/xmake.lua @@ -0,0 +1,131 @@ +--!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 ruki +-- @file xmake.lua +-- + +-- User Configs: +-- +-- The following `csharp.*` values can be set via `set_values()` in xmake.lua +-- to customize the auto-generated .csproj file. +-- +-- e.g. +-- target("example") +-- add_rules("csharp") +-- add_files("src/*.cs") +-- set_values("csharp.target_framework", "net8.0") +-- set_values("csharp.nullable", "disable") +-- set_values("csharp.allow_unsafe_blocks", "true") +-- +-- Project: +-- csharp.sdk - Project Sdk (default: Microsoft.NET.Sdk) +-- +-- General: +-- csharp.target_framework - e.g. "net8.0", auto-detected from dotnet sdk if not set +-- csharp.target_frameworks - multi-target, e.g. {"net8.0", "net9.0"} (list, semicolon-joined) +-- csharp.implicit_usings - ImplicitUsings (default: "enable") +-- csharp.nullable - Nullable (default: "enable") +-- csharp.lang_version - LangVersion, e.g. "12.0", "latest" +-- csharp.root_namespace - RootNamespace +-- csharp.enable_default_compile_items - EnableDefaultCompileItems (default: "false") +-- csharp.enable_default_embedded_resource_items - EnableDefaultEmbeddedResourceItems +-- csharp.enable_default_none_items - EnableDefaultNoneItems +-- +-- Build: +-- csharp.generate_assembly_info - GenerateAssemblyInfo +-- csharp.deterministic - Deterministic +-- csharp.prefer_32bit - Prefer32Bit +-- csharp.allow_unsafe_blocks - AllowUnsafeBlocks +-- csharp.check_for_overflow_underflow - CheckForOverflowUnderflow +-- +-- Analysis: +-- csharp.analysis_level - AnalysisLevel +-- csharp.enable_net_analyzers - EnableNETAnalyzers +-- csharp.enforce_code_style_in_build - EnforceCodeStyleInBuild +-- csharp.warnings_as_errors - WarningsAsErrors (list) +-- csharp.warnings_not_as_errors - WarningsNotAsErrors (list) +-- csharp.error_log - ErrorLog +-- csharp.generate_documentation_file - GenerateDocumentationFile +-- csharp.documentation_file - DocumentationFile +-- +-- Publish/Runtime: +-- csharp.runtime_identifier - RuntimeIdentifier, e.g. "win-x64" +-- csharp.runtime_identifiers - RuntimeIdentifiers (list) +-- csharp.self_contained - SelfContained +-- csharp.use_app_host - UseAppHost +-- csharp.roll_forward - RollForward +-- csharp.publish_single_file - PublishSingleFile +-- csharp.publish_trimmed - PublishTrimmed +-- csharp.trim_mode - TrimMode +-- csharp.publish_ready_to_run - PublishReadyToRun +-- csharp.invariant_globalization - InvariantGlobalization +-- csharp.include_native_libraries_for_self_extract - IncludeNativeLibrariesForSelfExtract +-- csharp.enable_compression_in_single_file - EnableCompressionInSingleFile +-- csharp.publish_aot - PublishAot +-- csharp.strip_symbols - StripSymbols +-- csharp.enable_trim_analyzer - EnableTrimAnalyzer +-- csharp.json_serializer_is_reflection_enabled_by_default - JsonSerializerIsReflectionEnabledByDefault +-- csharp.satellite_resource_languages - SatelliteResourceLanguages (list) +-- +-- Package Info: +-- csharp.version - Version +-- csharp.assembly_version - AssemblyVersion +-- csharp.file_version - FileVersion +-- csharp.informational_version - InformationalVersion +-- csharp.package_id - PackageId +-- csharp.authors - Authors +-- csharp.company - Company +-- csharp.product - Product +-- csharp.description - Description +-- csharp.copyright - Copyright +-- csharp.repository_url - RepositoryUrl +-- csharp.repository_type - RepositoryType +-- csharp.package_license_expression - PackageLicenseExpression +-- csharp.package_project_url - PackageProjectUrl +-- csharp.neutral_language - NeutralLanguage +-- csharp.enable_preview_features - EnablePreviewFeatures +-- +-- Output: +-- csharp.generate_runtime_configuration_files - GenerateRuntimeConfigurationFiles +-- csharp.copy_local_lock_file_assemblies - CopyLocalLockFileAssemblies +-- csharp.append_target_framework_to_output_path - AppendTargetFrameworkToOutputPath (default: "false") +-- csharp.append_runtime_identifier_to_output_path - AppendRuntimeIdentifierToOutputPath (default: "false") +-- csharp.produce_reference_assembly - ProduceReferenceAssembly +-- csharp.disable_implicit_framework_references - DisableImplicitFrameworkReferences +-- csharp.generate_target_framework_attribute - GenerateTargetFrameworkAttribute +-- +-- Custom Properties (for arbitrary csproj properties not listed above): +-- csharp.properties - add custom <PropertyGroup> entries, format: "Name=Value" +-- e.g. set_values("csharp.properties", "MyProp=value", "AnotherProp=value2") +-- +rule("csharp.build") + set_sourcekinds("cs") + on_load(function (target) + -- dotnet always outputs .dll for libraries, and no prefix + if target:is_shared() or target:is_static() then + target:set("prefixname", "") + target:set("extension", ".dll") + end + end) + on_config("config") + on_build("build") + on_install("install") + on_installcmd("installcmd") + +rule("csharp") + add_deps("csharp.build") + add_deps("utils.inherit.links") diff --git a/xmake/toolchains/dotnet/xmake.lua b/xmake/toolchains/dotnet/xmake.lua new file mode 100644 index 000000000..e26136a0a --- /dev/null +++ b/xmake/toolchains/dotnet/xmake.lua @@ -0,0 +1,65 @@ +--!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 ruki +-- @file xmake.lua +-- + +toolchain("dotnet") + set_homepage("https://dotnet.microsoft.com/") + set_description(".NET SDK Toolchain") + + set_toolset("cs", "dotnet") + set_toolset("csld", "dotnet") + set_toolset("cssh", "dotnet") + + on_check(function (toolchain) + import("detect.sdks.find_dotnet") + + -- find dotnet sdk from packages first + local sdkinfo + for _, package in ipairs(toolchain:packages()) do + local installdir = package:installdir() + if installdir and os.isdir(installdir) then + sdkinfo = find_dotnet(installdir, {force = true}) + if sdkinfo then + break + end + end + end + + -- find dotnet sdk from system + if not sdkinfo then + sdkinfo = find_dotnet() + end + if not sdkinfo or not sdkinfo.bindir then + return false + end + toolchain:config_set("bindir", sdkinfo.bindir) + toolchain:config_set("sdkdir", sdkinfo.sdkdir) + if sdkinfo.sdkver then + toolchain:config_set("sdkver", sdkinfo.sdkver) + end + return true + end) + + on_load(function (toolchain) + + -- set default environment variables + toolchain:add("runenvs", "DOTNET_NOLOGO", "1") + toolchain:add("runenvs", "DOTNET_CLI_TELEMETRY_OPTOUT", "1") + toolchain:add("runenvs", "DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "1") + end) |
