Skip to content

Commit 5c5fdd4

Browse files
committed
Fix prepost build step on Windows
1 parent 56552aa commit 5c5fdd4

File tree

6 files changed

+210
-28
lines changed

6 files changed

+210
-28
lines changed

modules/ninja/ninja_cpp.lua

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,9 +1165,12 @@ function m.linkTarget(cfg)
11651165
end
11661166
end
11671167

1168+
local postbuildStamp = nil
11681169
if hasPostBuild then
1169-
m.buildPostBuildEvents(cfg, targetPath)
1170+
postbuildStamp = m.buildPostBuildEvents(cfg, targetPath)
11701171
end
1172+
1173+
cfg._postbuildStamp = postbuildStamp
11711174
end
11721175

11731176
function m.buildPreBuildEvents(cfg)
@@ -1246,22 +1249,30 @@ function m.buildPostBuildEvents(cfg, targetPath)
12461249
return
12471250
end
12481251

1249-
local postbuildPhony = path.getrelative(cfg.workspace.location, cfg.buildtarget.directory) .. "/" .. cfg.project.name .. ".postbuild"
1252+
local stampFile = path.getrelative(cfg.workspace.location, cfg.buildtarget.directory) .. "/" .. cfg.project.name .. ".postbuild.stamp"
12501253

12511254
if hasMessage and not hasCommands then
1252-
_p("build %s: postbuildmessage | %s", postbuildPhony, targetPath)
1253-
_p(" postbuildmessage = \"%s\"", cfg.postbuildmessage)
1255+
local shell = os.shell()
1256+
local touchCmd = iif(shell == "cmd", "type nul > \"" .. stampFile .. "\"", "touch \"" .. stampFile .. "\"")
1257+
_p("build %s: postbuildmessage | %s", stampFile, targetPath)
1258+
_p(" postbuildmessage = \"%s && %s\"", cfg.postbuildmessage, touchCmd)
12541259
elseif hasCommands and not hasMessage then
12551260
local commands = os.translateCommandsAndPaths(cfg.postbuildcommands, cfg.project.basedir, cfg.project.location)
1256-
local cmdStr = table.concat(commands, " && ")
1257-
_p("build %s: postbuild | %s", postbuildPhony, targetPath)
1261+
local shell = _OPTIONS.shell or iif(cfg.system == p.WINDOWS, "cmd", "posix")
1262+
local touchCmd = iif(shell == "cmd", "type nul > \"" .. stampFile .. "\"", "touch \"" .. stampFile .. "\"")
1263+
local cmdStr = table.concat(commands, " && ") .. " && " .. touchCmd
1264+
_p("build %s: postbuild | %s", stampFile, targetPath)
12581265
_p(" postbuildcommands = %s", cmdStr)
12591266
else
12601267
local commands = os.translateCommandsAndPaths(cfg.postbuildcommands, cfg.project.basedir, cfg.project.location)
1261-
local cmdStr = "echo \"" .. cfg.postbuildmessage .. "\" && " .. table.concat(commands, " && ")
1262-
_p("build %s: postbuild | %s", postbuildPhony, targetPath)
1268+
local shell = os.shell()
1269+
local touchCmd = iif(shell == "cmd", "type nul > \"" .. stampFile .. "\"", "touch \"" .. stampFile .. "\"")
1270+
local cmdStr = "echo \"" .. cfg.postbuildmessage .. "\" && " .. table.concat(commands, " && ") .. " && " .. touchCmd
1271+
_p("build %s: postbuild | %s", stampFile, targetPath)
12631272
_p(" postbuildcommands = %s", cmdStr)
12641273
end
1274+
1275+
return stampFile
12651276
end
12661277

12671278
function m.buildTargets(prj)
@@ -1272,10 +1283,8 @@ function m.buildTargets(prj)
12721283
local targetPath = path.getrelative(cfg.workspace.location, cfg.buildtarget.directory) .. "/" .. cfg.buildtarget.name
12731284
local cfgName = ninja.key(cfg)
12741285

1275-
local hasPostBuild = #cfg.postbuildcommands > 0 or cfg.postbuildmessage
1276-
if hasPostBuild then
1277-
local postbuildTarget = path.getrelative(cfg.workspace.location, cfg.buildtarget.directory) .. "/" .. cfg.project.name .. ".postbuild"
1278-
_p("build %s: phony %s", cfgName, postbuildTarget)
1286+
if cfg._postbuildStamp then
1287+
_p("build %s: phony %s", cfgName, cfg._postbuildStamp)
12791288
else
12801289
_p("build %s: phony %s", cfgName, targetPath)
12811290
end
@@ -1290,13 +1299,10 @@ function m.projectPhonies(prj)
12901299

12911300
local firstCfg = project.getfirstconfig(prj)
12921301
if firstCfg then
1293-
local targetPath = path.getrelative(firstCfg.workspace.location, firstCfg.buildtarget.directory) .. "/" .. firstCfg.buildtarget.name
1294-
1295-
local hasPostBuild = #firstCfg.postbuildcommands > 0 or firstCfg.postbuildmessage
1296-
if hasPostBuild then
1297-
local postbuildTarget = path.getrelative(firstCfg.workspace.location, firstCfg.buildtarget.directory) .. "/" .. firstCfg.project.name .. ".postbuild"
1298-
_p("build %s: phony %s", prj.name, postbuildTarget)
1302+
if firstCfg._postbuildStamp then
1303+
_p("build %s: phony %s", prj.name, firstCfg._postbuildStamp)
12991304
else
1305+
local targetPath = path.getrelative(firstCfg.workspace.location, firstCfg.buildtarget.directory) .. "/" .. firstCfg.buildtarget.name
13001306
_p("build %s: phony %s", prj.name, targetPath)
13011307
end
13021308
end

modules/ninja/tests/_tests.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ return {
1818
"test_ninja_implib.lua",
1919
"test_ninja_pch.lua",
2020
"test_ninja_perfile_config.lua",
21+
"test_ninja_postbuild_stamp.lua",
2122
"test_ninja_project.lua",
2223
"test_ninja_tokens.lua",
2324
"test_ninja_workspace.lua",

modules/ninja/tests/test_ninja_build_commands.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,5 +192,6 @@ function suite.prebuildCommands_withQuotedPaths_onWindows()
192192
cpp.buildPreBuildEvents(cfg)
193193

194194
-- The command should have quotes around paths
195-
test.string_contains(premake.captured(), 'copy /B /Y')
195+
local captured = premake.captured()
196+
test.istrue(captured:find('copy /B /Y', 1, true) ~= nil)
196197
end

modules/ninja/tests/test_ninja_config.lua

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,7 @@ build bin/Debug/MyProject.prelinkevents: prelink obj/Debug/main.o obj/Debug/foo.
782782
--
783783

784784
function suite.postbuildEvents_onCommands()
785+
system "Linux"
785786
toolset "gcc"
786787
kind "ConsoleApp"
787788
files { "main.cpp" }
@@ -791,8 +792,8 @@ build bin/Debug/MyProject.prelinkevents: prelink obj/Debug/main.o obj/Debug/foo.
791792
cpp.buildPostBuildEvents(cfg, "bin/Debug/MyProject")
792793

793794
test.capture [[
794-
build bin/Debug/MyProject.postbuild: postbuild | bin/Debug/MyProject
795-
postbuildcommands = echo Done
795+
build bin/Debug/MyProject.postbuild.stamp: postbuild | bin/Debug/MyProject
796+
postbuildcommands = echo Done && touch "bin/Debug/MyProject.postbuild.stamp"
796797
]]
797798
end
798799

@@ -801,6 +802,7 @@ build bin/Debug/MyProject.postbuild: postbuild | bin/Debug/MyProject
801802
--
802803

803804
function suite.postbuildEvents_onMessage()
805+
system "Linux"
804806
toolset "gcc"
805807
kind "ConsoleApp"
806808
files { "main.cpp" }
@@ -810,8 +812,8 @@ build bin/Debug/MyProject.postbuild: postbuild | bin/Debug/MyProject
810812
cpp.buildPostBuildEvents(cfg, "bin/Debug/MyProject")
811813

812814
test.capture [[
813-
build bin/Debug/MyProject.postbuild: postbuildmessage | bin/Debug/MyProject
814-
postbuildmessage = "Build complete"
815+
build bin/Debug/MyProject.postbuild.stamp: postbuildmessage | bin/Debug/MyProject
816+
postbuildmessage = "Build complete && touch "bin/Debug/MyProject.postbuild.stamp""
815817
]]
816818
end
817819

@@ -820,6 +822,7 @@ build bin/Debug/MyProject.postbuild: postbuildmessage | bin/Debug/MyProject
820822
--
821823

822824
function suite.postbuildEvents_onMessageAndCommands()
825+
system "Linux"
823826
toolset "gcc"
824827
kind "ConsoleApp"
825828
files { "main.cpp" }
@@ -830,8 +833,8 @@ build bin/Debug/MyProject.postbuild: postbuildmessage | bin/Debug/MyProject
830833
cpp.buildPostBuildEvents(cfg, "bin/Debug/MyProject")
831834

832835
test.capture [[
833-
build bin/Debug/MyProject.postbuild: postbuild | bin/Debug/MyProject
834-
postbuildcommands = echo "Finishing build" && cp bin/Debug/MyProject /usr/local/bin/ && chmod +x /usr/local/bin/MyProject
836+
build bin/Debug/MyProject.postbuild.stamp: postbuild | bin/Debug/MyProject
837+
postbuildcommands = echo "Finishing build" && cp bin/Debug/MyProject /usr/local/bin/ && chmod +x /usr/local/bin/MyProject && touch "bin/Debug/MyProject.postbuild.stamp"
835838
]]
836839
end
837840

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
--
2+
-- test_ninja_postbuild_stamp.lua
3+
-- Test that postbuild events create stamp files to track execution.
4+
-- This prevents issues where postbuild commands modify/move the target file.
5+
-- Author: Nick Clark
6+
-- Copyright (c) 2025 Jess Perkins and the Premake project
7+
--
8+
9+
local suite = test.declare("ninja_postbuild_stamp")
10+
11+
local p = premake
12+
local ninja = p.modules.ninja
13+
local cpp = ninja.cpp
14+
15+
16+
--
17+
-- Setup and teardown
18+
--
19+
20+
local wks, prj
21+
22+
function suite.setup()
23+
p.action.set("ninja")
24+
wks, prj = test.createWorkspace()
25+
end
26+
27+
local function prepare()
28+
local cfg = test.getconfig(prj, "Debug")
29+
return cfg
30+
end
31+
32+
33+
---
34+
-- Stamp file tests
35+
---
36+
37+
--
38+
-- Check that postbuild commands create a stamp file on Windows
39+
--
40+
41+
function suite.postbuildStamp_onWindows()
42+
system "Windows"
43+
files { "test.cpp" }
44+
postbuildcommands { "echo Done" }
45+
46+
local cfg = prepare()
47+
local stampFile = cpp.buildPostBuildEvents(cfg, "bin/Debug/MyProject.exe")
48+
49+
-- Verify stamp file is created
50+
test.isequal("bin/Debug/MyProject.postbuild.stamp", stampFile)
51+
52+
-- Verify the build statement creates the stamp file
53+
test.capture [[
54+
build bin/Debug/MyProject.postbuild.stamp: postbuild | bin/Debug/MyProject.exe
55+
postbuildcommands = echo Done && type nul > "bin/Debug/MyProject.postbuild.stamp"
56+
]]
57+
end
58+
59+
60+
--
61+
-- Check that postbuild commands create a stamp file on Linux
62+
--
63+
64+
function suite.postbuildStamp_onLinux()
65+
system "Linux"
66+
files { "test.cpp" }
67+
postbuildcommands { "echo Done" }
68+
69+
local cfg = prepare()
70+
local stampFile = cpp.buildPostBuildEvents(cfg, "bin/Debug/MyProject")
71+
72+
-- Verify stamp file is created
73+
test.isequal("bin/Debug/MyProject.postbuild.stamp", stampFile)
74+
75+
-- Verify the build statement creates the stamp file using touch
76+
test.capture [[
77+
build bin/Debug/MyProject.postbuild.stamp: postbuild | bin/Debug/MyProject
78+
postbuildcommands = echo Done && touch "bin/Debug/MyProject.postbuild.stamp"
79+
]]
80+
end
81+
82+
83+
--
84+
-- Check that postbuild message also creates a stamp file
85+
--
86+
87+
function suite.postbuildStamp_onMessage()
88+
system "Linux"
89+
files { "test.cpp" }
90+
postbuildmessage "Build complete"
91+
92+
local cfg = prepare()
93+
local stampFile = cpp.buildPostBuildEvents(cfg, "bin/Debug/MyProject")
94+
95+
-- Verify stamp file is created
96+
test.isequal("bin/Debug/MyProject.postbuild.stamp", stampFile)
97+
98+
-- Verify the message includes stamp file creation
99+
test.capture [[
100+
build bin/Debug/MyProject.postbuild.stamp: postbuildmessage | bin/Debug/MyProject
101+
postbuildmessage = "Build complete && touch "bin/Debug/MyProject.postbuild.stamp""
102+
]]
103+
end
104+
105+
106+
--
107+
-- Check that postbuild with both message and commands creates stamp file
108+
--
109+
110+
function suite.postbuildStamp_onMessageAndCommands()
111+
system "Windows"
112+
files { "test.cpp" }
113+
postbuildmessage "Finishing"
114+
postbuildcommands { "copy file1.txt file2.txt" }
115+
116+
local cfg = prepare()
117+
local stampFile = cpp.buildPostBuildEvents(cfg, "bin/Debug/MyProject.exe")
118+
119+
-- Verify stamp file is created
120+
test.isequal("bin/Debug/MyProject.postbuild.stamp", stampFile)
121+
122+
-- Verify the command includes stamp file creation
123+
test.capture [[
124+
build bin/Debug/MyProject.postbuild.stamp: postbuild | bin/Debug/MyProject.exe
125+
postbuildcommands = echo "Finishing" && copy file1.txt file2.txt && type nul > "bin/Debug/MyProject.postbuild.stamp"
126+
]]
127+
end
128+
129+
130+
--
131+
-- Check that build targets reference the stamp file when postbuild is present
132+
--
133+
134+
function suite.buildTarget_referencesStampFile()
135+
system "Windows"
136+
files { "test.cpp" }
137+
postbuildcommands { "echo Done" }
138+
139+
-- Prepare the config
140+
local cfg = prepare()
141+
142+
-- Set up object files (normally done by buildFiles)
143+
cfg._objectFiles = { "obj/Debug/test.obj" }
144+
145+
-- Build the link target and postbuild
146+
cpp.linkTarget(cfg)
147+
148+
-- Verify the stamp file is set
149+
test.istrue(cfg._postbuildStamp ~= nil)
150+
test.isequal("bin/Debug/MyProject.postbuild.stamp", cfg._postbuildStamp)
151+
152+
-- Verify the build output includes stamp file
153+
local captured = premake.captured()
154+
test.istrue(captured:find("bin/Debug/MyProject.postbuild.stamp", 1, true) ~= nil)
155+
end
156+
157+
158+
--
159+
-- Check that projects without postbuild don't create stamp files
160+
--
161+
162+
function suite.noStampFile_withoutPostbuild()
163+
system "Windows"
164+
files { "test.cpp" }
165+
166+
local cfg = prepare()
167+
local stampFile = cpp.buildPostBuildEvents(cfg, "bin/Debug/MyProject.exe")
168+
169+
-- Verify no stamp file is created
170+
test.isnil(stampFile)
171+
end

modules/ninja/tests/test_ninja_project.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ end
147147
test.capture(string.format([[
148148
build bin/Debug/%s: link obj/Debug/main.o
149149
ldflags = $ldflags_%s_Debug
150-
build bin/Debug/%s.postbuild: postbuild | bin/Debug/%s
151-
postbuildcommands = echo Done
152-
]], targetName, prj.name, prj.name, targetName))
150+
build bin/Debug/%s.postbuild.stamp: postbuild | bin/Debug/%s
151+
postbuildcommands = echo Done && type nul > "bin/Debug/%s.postbuild.stamp"
152+
]], targetName, prj.name, prj.name, targetName, prj.name))
153153
end
154154

155155

0 commit comments

Comments
 (0)