Skip to content

Commit 5270987

Browse files
committed
Fixes for Ninja on Windows
1 parent a73d125 commit 5270987

File tree

5 files changed

+661
-8
lines changed

5 files changed

+661
-8
lines changed

modules/ninja/ninja_cpp.lua

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,13 @@ end
182182

183183
function m.prebuildcommandsrule(cfg, toolset)
184184
_p("rule prebuild")
185-
_p(" command = $prebuildcommands")
185+
-- Use cmd /c on Windows to ensure proper command execution with complex commands
186+
-- This matches the behavior of the gmake generator which detects shell type
187+
if cfg.system == p.WINDOWS then
188+
_p(" command = cmd /c $prebuildcommands")
189+
else
190+
_p(" command = $prebuildcommands")
191+
end
186192
_p(" description = Running pre-build commands")
187193
_p("")
188194
end
@@ -196,7 +202,12 @@ end
196202

197203
function m.prelinkcommandsrule(cfg, toolset)
198204
_p("rule prelink")
199-
_p(" command = $prelinkcommands")
205+
-- Use cmd /c on Windows to ensure proper command execution
206+
if cfg.system == p.WINDOWS then
207+
_p(" command = cmd /c $prelinkcommands")
208+
else
209+
_p(" command = $prelinkcommands")
210+
end
200211
_p(" description = Running pre-link commands")
201212
_p("")
202213
end
@@ -210,7 +221,12 @@ end
210221

211222
function m.postbuildcommandsrule(cfg, toolset)
212223
_p("rule postbuild")
213-
_p(" command = $postbuildcommands")
224+
-- Use cmd /c on Windows to ensure proper command execution
225+
if cfg.system == p.WINDOWS then
226+
_p(" command = cmd /c $postbuildcommands")
227+
else
228+
_p(" command = $postbuildcommands")
229+
end
214230
_p(" description = Running post-build commands")
215231
_p("")
216232
end
@@ -436,7 +452,7 @@ function m.getFileCxxFlags(cfg, filecfg, toolset)
436452
local allExternalIncludedirs = table.join(cfg.externalincludedirs or {}, filecfg.externalincludedirs or {})
437453
local allFrameworkdirs = table.join(cfg.frameworkdirs or {}, filecfg.frameworkdirs or {})
438454
local allIncludedirsafter = table.join(cfg.includedirsafter or {}, filecfg.includedirsafter or {})
439-
local includedirs = toolset.getincludedirs(cfg, allIncludedirs, allExternalIncludedirs, allFrameworkdirs, allIncludedirsafter)
455+
local includedirs = toolset.getincludedirs(cfg, allIncludedirs, allExternalIncludedDirs, allFrameworkdirs, allIncludedirsafter)
440456
flags = table.join(flags, includedirs)
441457

442458
local forceincludes = toolset.getforceincludes(filecfg)
@@ -539,6 +555,13 @@ function m.getLdFlags(cfg, toolset)
539555
flags = table.join(flags, rpaths)
540556
end
541557

558+
-- For MSVC shared libraries, add /IMPLIB: to specify the import library location
559+
-- MSVC is Windows-only, so no need to check cfg.system
560+
if cfg.kind == p.SHAREDLIB and toolset == p.tools.msc and not cfg.flags.NoImportLib then
561+
local impLibPath = path.getrelative(cfg.workspace.location, cfg.linktarget.directory) .. "/" .. cfg.linktarget.name
562+
table.insert(flags, "/IMPLIB:" .. impLibPath)
563+
end
564+
542565
return flags
543566
end
544567

@@ -1112,7 +1135,16 @@ function m.linkTarget(cfg)
11121135

11131136
local hasPostBuild = #cfg.postbuildcommands > 0 or cfg.postbuildmessage
11141137

1115-
_p("build %s: %s %s%s", targetPath, rule, table.concat(cfg._objectFiles, " "), implicitDeps)
1138+
-- For MSVC shared libraries with import libraries, list the import library as an implicit output
1139+
-- Dependencies will reference the .lib file (linktarget), which Ninja will know how to build.
1140+
-- MSVC is Windows-only, so no need to check cfg.system
1141+
local implicitOutputs = ""
1142+
if cfg.kind == p.SHAREDLIB and toolset == p.tools.msc and not cfg.flags.NoImportLib then
1143+
local impLibPath = path.getrelative(cfg.workspace.location, cfg.linktarget.directory) .. "/" .. cfg.linktarget.name
1144+
implicitOutputs = " | " .. impLibPath
1145+
end
1146+
1147+
_p("build %s%s: %s %s%s", targetPath, implicitOutputs, rule, table.concat(cfg._objectFiles, " "), implicitDeps)
11161148

11171149
if cfg.kind ~= p.STATICLIB then
11181150
_p(" ldflags = $ldflags_%s", ninja.key(cfg))

modules/ninja/tests/_tests.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ return {
1515
"test_ninja_custom_build.lua",
1616
"test_ninja_custom_rules.lua",
1717
"test_ninja_dependson.lua",
18+
"test_ninja_implib.lua",
1819
"test_ninja_pch.lua",
1920
"test_ninja_perfile_config.lua",
2021
"test_ninja_project.lua",
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
--
2+
-- test_ninja_build_commands.lua
3+
-- Test pre-build, pre-link, and post-build commands for Ninja.
4+
-- Author: Nick Clark
5+
-- Copyright (c) 2025 Jess Perkins and the Premake project
6+
--
7+
8+
local suite = test.declare("ninja_build_commands")
9+
10+
local p = premake
11+
local ninja = p.modules.ninja
12+
local cpp = ninja.cpp
13+
14+
15+
--
16+
-- Setup and teardown
17+
--
18+
19+
local wks, prj
20+
21+
function suite.setup()
22+
p.action.set("ninja")
23+
wks, prj = test.createWorkspace()
24+
end
25+
26+
local function prepare()
27+
local cfg = test.getconfig(prj, "Debug")
28+
return cfg
29+
end
30+
31+
32+
---
33+
-- Pre-build command tests
34+
---
35+
36+
--
37+
-- Check that prebuild commands use cmd /c on Windows
38+
--
39+
40+
function suite.prebuildCommands_usesCmdOnWindows()
41+
system "Windows"
42+
files { "test.cpp" }
43+
prebuildcommands { "echo test" }
44+
45+
local cfg = prepare()
46+
cpp.buildPreBuildEvents(cfg)
47+
48+
test.capture [[
49+
build bin/Debug/MyProject.prebuild: prebuild
50+
prebuildcommands = echo test
51+
]]
52+
end
53+
54+
55+
--
56+
-- Check that prebuild commands don't use cmd /c on Linux
57+
--
58+
59+
function suite.prebuildCommands_noCmdOnLinux()
60+
system "Linux"
61+
files { "test.cpp" }
62+
prebuildcommands { "echo test" }
63+
64+
local cfg = prepare()
65+
cpp.buildPreBuildEvents(cfg)
66+
67+
test.capture [[
68+
build bin/Debug/MyProject.prebuild: prebuild
69+
prebuildcommands = echo test
70+
]]
71+
end
72+
73+
74+
--
75+
-- Check that prebuild with message and commands combines them properly
76+
--
77+
78+
function suite.prebuildMessageAndCommands_onWindows()
79+
system "Windows"
80+
files { "test.cpp" }
81+
prebuildmessage "Running prebuild"
82+
prebuildcommands { "echo test" }
83+
84+
local cfg = prepare()
85+
cpp.buildPreBuildEvents(cfg)
86+
87+
test.capture [[
88+
build bin/Debug/MyProject.prebuild: prebuild
89+
prebuildcommands = echo "Running prebuild" && echo test
90+
]]
91+
end
92+
93+
94+
---
95+
-- Pre-link command tests
96+
---
97+
98+
--
99+
-- Check that prelink commands use cmd /c on Windows
100+
--
101+
102+
function suite.prelinkCommands_usesCmdOnWindows()
103+
system "Windows"
104+
files { "test.cpp" }
105+
prelinkcommands { "echo test" }
106+
107+
local cfg = prepare()
108+
cfg._objectFiles = { "obj/Debug/test.obj" }
109+
cpp.buildPreLinkEvents(cfg, cfg._objectFiles)
110+
111+
test.capture [[
112+
build bin/Debug/MyProject.prelinkevents: prelink obj/Debug/test.obj
113+
prelinkcommands = echo test
114+
]]
115+
end
116+
117+
118+
--
119+
-- Check that prelink commands don't use cmd /c on Linux
120+
--
121+
122+
function suite.prelinkCommands_noCmdOnLinux()
123+
system "Linux"
124+
files { "test.cpp" }
125+
prelinkcommands { "echo test" }
126+
127+
local cfg = prepare()
128+
cfg._objectFiles = { "obj/Debug/test.obj" }
129+
cpp.buildPreLinkEvents(cfg, cfg._objectFiles)
130+
131+
test.capture [[
132+
build bin/Debug/MyProject.prelinkevents: prelink obj/Debug/test.obj
133+
prelinkcommands = echo test
134+
]]
135+
end
136+
137+
138+
---
139+
-- Post-build command tests
140+
---
141+
142+
--
143+
-- Check that postbuild commands use cmd /c on Windows
144+
--
145+
146+
function suite.postbuildCommands_usesCmdOnWindows()
147+
system "Windows"
148+
files { "test.cpp" }
149+
postbuildcommands { "echo test" }
150+
151+
local cfg = prepare()
152+
local targetPath = "bin/Debug/MyProject.exe"
153+
cpp.buildPostBuildEvents(cfg, targetPath)
154+
155+
test.capture [[
156+
build bin/Debug/MyProject.postbuild: postbuild | bin/Debug/MyProject.exe
157+
postbuildcommands = echo test
158+
]]
159+
end
160+
161+
162+
--
163+
-- Check that postbuild commands don't use cmd /c on Linux
164+
--
165+
166+
function suite.postbuildCommands_noCmdOnLinux()
167+
system "Linux"
168+
files { "test.cpp" }
169+
postbuildcommands { "echo test" }
170+
171+
local cfg = prepare()
172+
local targetPath = "bin/Debug/MyProject"
173+
cpp.buildPostBuildEvents(cfg, targetPath)
174+
175+
test.capture [[
176+
build bin/Debug/MyProject.postbuild: postbuild | bin/Debug/MyProject
177+
postbuildcommands = echo test
178+
]]
179+
end
180+
181+
182+
--
183+
-- Check that complex commands with quotes work on Windows
184+
--
185+
186+
function suite.prebuildCommands_withQuotedPaths_onWindows()
187+
system "Windows"
188+
files { "test.cpp" }
189+
prebuildcommands { "{COPYFILE} %[src/file.txt] %[dest/file.txt]" }
190+
191+
local cfg = prepare()
192+
cpp.buildPreBuildEvents(cfg)
193+
194+
-- The command should have quotes around paths
195+
test.string_contains(premake.captured(), 'copy /B /Y')
196+
end

modules/ninja/tests/test_ninja_build_rules.lua

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -397,11 +397,12 @@ rule copy
397397
--
398398

399399
function suite.prebuildCommandsRule()
400+
system "Windows"
400401
local cfg = prepare()
401402
cpp.prebuildcommandsrule(cfg)
402403
test.capture [[
403404
rule prebuild
404-
command = $prebuildcommands
405+
command = cmd /c $prebuildcommands
405406
description = Running pre-build commands
406407
407408
]]
@@ -429,11 +430,12 @@ rule prebuildmessage
429430
--
430431

431432
function suite.prelinkCommandsRule()
433+
system "Windows"
432434
local cfg = prepare()
433435
cpp.prelinkcommandsrule(cfg)
434436
test.capture [[
435437
rule prelink
436-
command = $prelinkcommands
438+
command = cmd /c $prelinkcommands
437439
description = Running pre-link commands
438440
439441
]]
@@ -461,11 +463,12 @@ rule prelinkmessage
461463
--
462464

463465
function suite.postbuildCommandsRule()
466+
system "Windows"
464467
local cfg = prepare()
465468
cpp.postbuildcommandsrule(cfg)
466469
test.capture [[
467470
rule postbuild
468-
command = $postbuildcommands
471+
command = cmd /c $postbuildcommands
469472
description = Running post-build commands
470473
471474
]]
@@ -488,6 +491,57 @@ rule postbuildmessage
488491
end
489492

490493

494+
--
495+
-- Check the pre-build commands rule on Linux doesn't use cmd /c.
496+
--
497+
498+
function suite.prebuildCommandsRule_onLinux()
499+
system "Linux"
500+
local cfg = prepare()
501+
cpp.prebuildcommandsrule(cfg)
502+
test.capture [[
503+
rule prebuild
504+
command = $prebuildcommands
505+
description = Running pre-build commands
506+
507+
]]
508+
end
509+
510+
511+
--
512+
-- Check the pre-link commands rule on Linux doesn't use cmd /c.
513+
--
514+
515+
function suite.prelinkCommandsRule_onLinux()
516+
system "Linux"
517+
local cfg = prepare()
518+
cpp.prelinkcommandsrule(cfg)
519+
test.capture [[
520+
rule prelink
521+
command = $prelinkcommands
522+
description = Running pre-link commands
523+
524+
]]
525+
end
526+
527+
528+
--
529+
-- Check the post-build commands rule on Linux doesn't use cmd /c.
530+
--
531+
532+
function suite.postbuildCommandsRule_onLinux()
533+
system "Linux"
534+
local cfg = prepare()
535+
cpp.postbuildcommandsrule(cfg)
536+
test.capture [[
537+
rule postbuild
538+
command = $postbuildcommands
539+
description = Running post-build commands
540+
541+
]]
542+
end
543+
544+
491545
--
492546
-- Check the custom command rule.
493547
--

0 commit comments

Comments
 (0)