Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 375d620

Browse files
committedFeb 9, 2025··
buildtool: functionalize, use incremental build
test:subprocess: env make own executable
1 parent 39a77e8 commit 375d620

File tree

5 files changed

+106
-97
lines changed

5 files changed

+106
-97
lines changed
 

‎.github/workflows/ci-nojvm.yml

+4
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,8 @@ jobs:
3434

3535
- uses: ./.github/workflows/composite-install-matlab
3636

37+
- name: Fortran FC
38+
if: runner.os == 'macOS'
39+
run: echo "FC=gfortran-14" >> $GITHUB_ENV
40+
3741
- uses: ./.github/workflows/composite-buildtool

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ docs/
1010
*.mex*
1111
*.oct
1212

13+
test/printenv.exe
1314
test/stdout_stderr_c.exe
1415
test/stdout_stderr_fortran.exe
1516
test/stdin_cpp.exe

‎buildfile.m

+69-88
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
addpath(plan.RootFolder)
1111

12+
%% Self-test setup
1213
if isMATLABReleaseOlderThan("R2023b")
1314
plan("test") = matlab.buildtool.Task(Actions=@legacy_test);
1415
elseif isMATLABReleaseOlderThan("R2024a")
@@ -19,19 +20,25 @@
1920
plan("test") = matlab.buildtool.tasks.TestTask("test", Strict=false, TestResults="TestResults.xml");
2021
end
2122

23+
td = plan.RootFolder + "/test";
24+
srcs = [td+"/stdout_stderr_c.c", td+"/stdin_cpp.cpp", td+"/printenv.cpp"];
25+
exes = [td+"/stdout_stderr_c.exe", td+"/stdin_cpp.exe", td+"/printenv.exe"];
26+
if ~isempty(get_compiler("fortran"))
27+
srcs = [srcs, td + "/stdout_stderr_fortran.f90", td + "/stdin_fortran.f90"];
28+
exes = [exes, td+"/stdout_stderr_fortran.exe", td+"/stdin_fortran.exe"];
29+
end
30+
plan("exe") = matlab.buildtool.Task(Inputs=srcs, Outputs=exes, Actions=@build_exe);
31+
plan("test").Dependencies = "exe";
32+
2233
if ~isMATLABReleaseOlderThan("R2023b")
2334
plan("clean") = matlab.buildtool.tasks.CleanTask;
2435
end
2536

26-
plan("build_c") = matlab.buildtool.Task(Actions=@subprocess_build_c);
27-
plan("build_cpp") = matlab.buildtool.Task(Actions=@subprocess_build_cpp);
28-
plan("build_fortran") = matlab.buildtool.Task(Actions=@subprocess_build_fortran);
29-
plan("test").Dependencies = ["build_c", "build_cpp", "build_fortran"];
30-
3137
if ~isMATLABReleaseOlderThan("R2024a")
3238
plan("check") = matlab.buildtool.tasks.CodeIssuesTask(pkg_name, IncludeSubfolders=true, ...
3339
WarningThreshold=0, Results="CodeIssues.sarif");
34-
plan("coverage") = matlab.buildtool.tasks.TestTask(Description="code coverage", Dependencies="clean", SourceFiles="test", Strict=false, CodeCoverageResults="code-coverage.xml");
40+
41+
plan("coverage") = matlab.buildtool.tasks.TestTask(Description="code coverage", Dependencies="clean", SourceFiles="test", Strict=false, CodeCoverageResults="code-coverage.xml");
3542
end
3643

3744
%% MexTask
@@ -51,11 +58,8 @@
5158
% name of MEX target function is name of first source file
5259
if isMATLABReleaseOlderThan("R2024b")
5360
mex_name = "mex_" + name;
54-
% specifying .Inputs and .Outputs enables incremental builds
55-
% https://www.mathworks.com/help/matlab/matlab_prog/improve-performance-with-incremental-builds.html
56-
plan(mex_name) = matlab.buildtool.Task(Actions=@(context) legacy_mex(context, compiler_opt, linker_opt));
57-
plan(mex_name).Inputs = src;
58-
plan(mex_name).Outputs = fullfile(bindir, name + "." + mexext());
61+
plan(mex_name) = matlab.buildtool.Task(Inputs=src, Outputs=fullfile(bindir, name + "." + mexext()), ...
62+
Actions=@(context) legacy_mex(context, compiler_opt, linker_opt));
5963
mex_deps(end+1) = mex_name; %#ok<AGROW>
6064
else
6165
plan("mex:" + name) = matlab.buildtool.tasks.MexTask(src, bindir, ...
@@ -96,112 +100,89 @@ function publishTask(context)
96100
end
97101

98102

99-
function subprocess_build_c(context)
103+
function build_exe(context)
100104

101-
td = context.Plan.RootFolder + "/test";
102-
src = td + "/stdout_stderr_c.c";
105+
for i = 1:length(context.Task.Inputs)
106+
src = context.Task.Inputs(i);
107+
exe = context.Task.Outputs(i).paths;
108+
exe = exe(1);
103109

104-
for s = src
105-
[~, n] = fileparts(s);
106-
exe = fullfile(td, n + ".exe");
107-
if stdlib.get_modtime(s) < stdlib.get_modtime(exe)
108-
continue
110+
[~,~,ext] = fileparts(src.paths);
111+
switch ext
112+
case ".c", lang = "c";
113+
case ".cpp", lang = "c++";
114+
case ".f90", lang = "fortran";
115+
otherwise, error("unknown code suffix " + ext)
109116
end
110117

111-
cmd = get_build_cmd("c++", s);
112-
if isempty(cmd), return, end
113-
cmd = cmd + exe;
114-
disp(cmd)
115-
[r, m] = system(cmd);
116-
if r ~= 0
117-
disp("failed to build TestSubprocess " + exe + " " + m)
118-
end
119-
end
120-
121-
end
122-
123-
124-
function subprocess_build_cpp(context)
125-
126-
td = context.Plan.RootFolder + "/test";
127-
src = [td + "/sleep.cpp", td + "/stdin_cpp.cpp"];
118+
[comp, shell, outFlag] = get_build_cmd(lang);
119+
if isempty(comp), return, end
128120

129-
for s = src
130-
[~, n] = fileparts(s);
131-
exe = fullfile(td, n + ".exe");
132-
if stdlib.get_modtime(s) < stdlib.get_modtime(exe)
133-
continue
121+
cmd = join([comp, src.paths, outFlag + exe]);
122+
if ~isempty(shell)
123+
cmd = join([shell, "&&", cmd]);
134124
end
135125

136-
cmd = get_build_cmd("c++", s);
137-
if isempty(cmd), return, end
138-
cmd = cmd + exe;
139126
disp(cmd)
140-
[r, m] = system(cmd);
141-
if r ~= 0
142-
disp("failed to build TestSubprocess " + exe + " " + m)
143-
end
127+
system(cmd);
144128
end
145129

146130
end
147131

148132

149-
function subprocess_build_fortran(context)
133+
function [comp, shell] = get_compiler(lang)
150134

151-
td = context.Plan.RootFolder + "/test";
152-
src = [td + "/stdout_stderr_fortran.f90", td + "/stdin_fortran.f90"];
135+
lang = lower(lang);
153136

154-
for s = src
155-
[~, n] = fileparts(s);
156-
exe = fullfile(td, n + ".exe");
157-
if stdlib.get_modtime(s) < stdlib.get_modtime(exe)
158-
continue
137+
co = mex.getCompilerConfigurations(lang);
138+
139+
if isempty(co)
140+
switch lang
141+
case "fortran"
142+
comp = getenv("FC");
143+
if isempty(comp)
144+
disp("set FC environment variable to the Fortran compiler path, or do 'mex -setup fortran'")
145+
end
146+
case "c++"
147+
comp = getenv("CXX");
148+
if isempty(comp)
149+
disp("set CXX environment variable to the C++ compiler path, or do 'mex -setup c++")
150+
end
151+
case "c"
152+
comp = getenv("CC");
153+
if isempty(comp)
154+
disp("set CC environment variable to the C compiler path, or do 'mex -setup c'")
155+
end
156+
otherwise, error("language not known " + lang)
159157
end
158+
else
159+
comp = co.Details.CompilerExecutable;
160+
disp(lang + " compiler: " + co.ShortName + " " + co.Name + " " + co.Version + " " + comp)
161+
end
160162

161-
cmd = get_build_cmd("Fortran", s);
162-
if isempty(cmd), return, end
163-
cmd = cmd + exe;
164-
disp(cmd)
165-
[r, m] = system(cmd);
166-
if r ~= 0
167-
disp("failed to build TestSubprocess " + exe + " " + m)
163+
164+
shell = string.empty;
165+
if ispc
166+
disp("Shell: " + co.Details.CommandLineShell)
167+
if any(startsWith(co.ShortName, ["INTEL", "MSVC"]))
168+
shell = join([strcat('"',string(co.Details.CommandLineShell),'"'), ...
169+
co.Details.CommandLineShellArg], " ");
168170
end
169171
end
170172

171173
end
172174

173175

174-
function cmd = get_build_cmd(lang, src)
176+
function [comp, shell, outFlag] = get_build_cmd(lang)
175177

176-
cmd = string.empty;
178+
outFlag = "-o";
177179

178-
co = mex.getCompilerConfigurations(lang);
179-
if isempty(co)
180-
if lang == "Fortran"
181-
fc = getenv("FC");
182-
if isempty(fc)
183-
disp("set FC environment variable to the Fortran compiler executable, or do 'mex -setup fortran' to configure the Fortran compiler")
184-
end
185-
end
186-
disp(lang + " compiler not found")
187-
return
188-
else
189-
comp = co.Details.CompilerExecutable;
190-
end
180+
[comp, shell] = get_compiler(lang);
191181

192-
outFlag = "-o";
193-
shell = string.empty;
194-
msvcLike = ispc && (contains(co.Name, "Visual Studio"));
195-
if msvcLike
196-
shell = join([strcat('"',string(co.Details.CommandLineShell),'"'), ...
197-
co.Details.CommandLineShellArg], " ");
182+
if ~isempty(shell)
198183
outFlag = "/Fo" + tempdir + " /link /out:";
199184
end
200185

201-
cmd = join([comp, src, outFlag]);
202-
if ~isempty(shell)
203-
cmd = join([shell, "&&", cmd]);
204-
end
205186

206187
end
207188

‎test/TestSubprocess.m

+6-9
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,19 @@ function test_cwd(tc)
6868

6969

7070
function test_env_run(tc)
71+
import matlab.unittest.constraints.IsFile
72+
73+
cwd = fileparts(mfilename('fullpath'));
74+
exe = cwd + "/printenv.exe";
75+
tc.assumeThat(exe, IsFile, exe + " not found")
7176

7277
names = ["TEST1", "TEST2"];
7378
vals = ["test123", "test321"];
7479

7580
env = struct(names(1), vals(1), names(2), vals(2));
7681

7782
for i = 1:length(names)
78-
if ispc
79-
% in ComSpec, echo is a special shell cmd like "dir" -- also doesn't
80-
% work in python subprocess.run
81-
cmd = ["pwsh", "-c", "(Get-ChildItem Env:" + names(i) + ").Value"];
82-
else
83-
cmd = ["sh", "-c", "echo $" + names(i)];
84-
end
85-
86-
[ret, out] = stdlib.subprocess_run(cmd, "env", env);
83+
[ret, out] = stdlib.subprocess_run([exe, names(i)], env=env);
8784
tc.verifyEqual(ret, 0)
8885
tc.verifySubstring(out, vals(i))
8986
end

‎test/printenv.cpp

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <cstdlib>
2+
#include <iostream>
3+
4+
#ifdef _MSC_VER
5+
#pragma warning(disable : 4996)
6+
#endif
7+
8+
9+
int main(int argc, char *argv[]) {
10+
11+
if (argc < 2) {
12+
std::cerr << "Specify environment variable name to print\n";
13+
return EXIT_FAILURE;
14+
}
15+
16+
char *value = std::getenv(argv[1]);
17+
18+
if (value) {
19+
std::cout << value << "\n";
20+
return EXIT_SUCCESS;
21+
}
22+
23+
std::cerr << "Environment variable " << argv[1] << " not found\n";
24+
return EXIT_FAILURE;
25+
26+
}

0 commit comments

Comments
 (0)
Please sign in to comment.