powertrain-build 1.13.1__py3-none-any.whl → 1.13.3.dev3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- powertrain_build/__init__.py +40 -40
- powertrain_build/__main__.py +6 -6
- powertrain_build/a2l.py +582 -582
- powertrain_build/a2l_merge.py +650 -650
- powertrain_build/a2l_templates.py +717 -717
- powertrain_build/build.py +985 -985
- powertrain_build/build_defs.py +309 -309
- powertrain_build/build_proj_config.py +690 -690
- powertrain_build/check_interface.py +575 -575
- powertrain_build/cli.py +141 -141
- powertrain_build/config.py +542 -542
- powertrain_build/core.py +395 -395
- powertrain_build/core_dummy.py +343 -343
- powertrain_build/create_conversion_table.py +73 -73
- powertrain_build/dids.py +916 -916
- powertrain_build/dummy.py +157 -157
- powertrain_build/dummy_spm.py +252 -252
- powertrain_build/environmentcheck.py +52 -52
- powertrain_build/ext_dbg.py +255 -255
- powertrain_build/ext_var.py +327 -327
- powertrain_build/feature_configs.py +301 -301
- powertrain_build/gen_allsysteminfo.py +227 -227
- powertrain_build/gen_label_split.py +449 -449
- powertrain_build/handcode_replacer.py +124 -124
- powertrain_build/html_report.py +133 -133
- powertrain_build/interface/__init__.py +4 -4
- powertrain_build/interface/application.py +511 -511
- powertrain_build/interface/base.py +500 -500
- powertrain_build/interface/csp_api.py +490 -490
- powertrain_build/interface/device_proxy.py +677 -677
- powertrain_build/interface/ems.py +67 -67
- powertrain_build/interface/export_global_vars.py +121 -121
- powertrain_build/interface/generate_adapters.py +132 -132
- powertrain_build/interface/generate_hi_interface.py +87 -87
- powertrain_build/interface/generate_service.py +69 -69
- powertrain_build/interface/generate_wrappers.py +147 -147
- powertrain_build/interface/generation_utils.py +142 -142
- powertrain_build/interface/hal.py +194 -194
- powertrain_build/interface/model_yaml_verification.py +348 -348
- powertrain_build/interface/service.py +296 -296
- powertrain_build/interface/simulink.py +249 -249
- powertrain_build/interface/update_call_sources.py +180 -180
- powertrain_build/interface/update_model_yaml.py +186 -186
- powertrain_build/interface/zone_controller.py +362 -362
- powertrain_build/lib/__init__.py +4 -4
- powertrain_build/lib/helper_functions.py +127 -127
- powertrain_build/lib/logger.py +55 -55
- powertrain_build/matlab_scripts/CodeGen/BuildAutomationPyBuild.m +78 -78
- powertrain_build/matlab_scripts/CodeGen/Generate_A2L.m +154 -154
- powertrain_build/matlab_scripts/CodeGen/generateTLUnit.m +239 -239
- powertrain_build/matlab_scripts/CodeGen/getAsilClassification.m +28 -28
- powertrain_build/matlab_scripts/CodeGen/modelConfiguredForTL.m +28 -28
- powertrain_build/matlab_scripts/CodeGen/moveDefOutports.m +88 -88
- powertrain_build/matlab_scripts/CodeGen/parseCalMeasData.m +410 -410
- powertrain_build/matlab_scripts/CodeGen/parseCoreIdentifiers.m +139 -139
- powertrain_build/matlab_scripts/CodeGen/parseDIDs.m +141 -141
- powertrain_build/matlab_scripts/CodeGen/parseInPorts.m +106 -106
- powertrain_build/matlab_scripts/CodeGen/parseIncludeConfigs.m +25 -25
- powertrain_build/matlab_scripts/CodeGen/parseModelInfo.m +38 -38
- powertrain_build/matlab_scripts/CodeGen/parseNVM.m +81 -81
- powertrain_build/matlab_scripts/CodeGen/parseOutPorts.m +120 -120
- powertrain_build/matlab_scripts/CodeGen/parsePreProcBlks.m +23 -23
- powertrain_build/matlab_scripts/CodeGen/struct2JSON.m +128 -128
- powertrain_build/matlab_scripts/CodeGen/updateCodeSwConfig.m +31 -31
- powertrain_build/matlab_scripts/Init_PyBuild.m +91 -91
- powertrain_build/matlab_scripts/__init__.py +2 -2
- powertrain_build/matlab_scripts/helperFunctions/Get_Full_Name.m +46 -46
- powertrain_build/matlab_scripts/helperFunctions/Get_SrcLines.m +12 -12
- powertrain_build/matlab_scripts/helperFunctions/Init_Models.m +78 -78
- powertrain_build/matlab_scripts/helperFunctions/Init_Projects.m +67 -67
- powertrain_build/matlab_scripts/helperFunctions/Read_Units.m +34 -34
- powertrain_build/matlab_scripts/helperFunctions/SetProjectTimeSamples.m +26 -26
- powertrain_build/matlab_scripts/helperFunctions/Strip_Suffix.m +16 -16
- powertrain_build/matlab_scripts/helperFunctions/followLink.m +118 -118
- powertrain_build/matlab_scripts/helperFunctions/getCodeSwitches.m +50 -50
- powertrain_build/matlab_scripts/helperFunctions/getConsumerBlocks.m +30 -30
- powertrain_build/matlab_scripts/helperFunctions/getDefBlock.m +39 -39
- powertrain_build/matlab_scripts/helperFunctions/getDefOutport.m +58 -58
- powertrain_build/matlab_scripts/helperFunctions/getDstBlocks.m +19 -19
- powertrain_build/matlab_scripts/helperFunctions/getDstLines.m +13 -13
- powertrain_build/matlab_scripts/helperFunctions/getInterfaceSignals.m +37 -37
- powertrain_build/matlab_scripts/helperFunctions/getName.m +37 -37
- powertrain_build/matlab_scripts/helperFunctions/getPath.m +6 -6
- powertrain_build/matlab_scripts/helperFunctions/getProperValue.m +21 -21
- powertrain_build/matlab_scripts/helperFunctions/getSrcBlocks.m +19 -19
- powertrain_build/matlab_scripts/helperFunctions/getSrcLines.m +13 -13
- powertrain_build/matlab_scripts/helperFunctions/loadLibraries.m +10 -10
- powertrain_build/matlab_scripts/helperFunctions/loadjson.m +6 -6
- powertrain_build/matlab_scripts/helperFunctions/modifyEnumStructField.m +21 -21
- powertrain_build/matlab_scripts/helperFunctions/removeConfigDuplicates.m +31 -31
- powertrain_build/matlab_scripts/helperFunctions/sortSystemByClass.m +26 -26
- powertrain_build/matlab_scripts/helperFunctions/tl_getfast.m +89 -89
- powertrain_build/matlab_scripts/helperFunctions/topLevelSystem.m +20 -20
- powertrain_build/matlab_scripts/helperFunctions/updateModels.m +131 -131
- powertrain_build/memory_section.py +224 -224
- powertrain_build/nvm_def.py +729 -729
- powertrain_build/problem_logger.py +86 -86
- powertrain_build/pt_matlab.py +430 -430
- powertrain_build/pt_win32.py +144 -144
- powertrain_build/replace_compu_tab_ref.py +105 -105
- powertrain_build/rte_dummy.py +254 -254
- powertrain_build/sched_funcs.py +209 -207
- powertrain_build/signal.py +7 -7
- powertrain_build/signal_if_html_rep.py +221 -221
- powertrain_build/signal_if_html_rep_all.py +302 -302
- powertrain_build/signal_incons_html_rep.py +180 -180
- powertrain_build/signal_incons_html_rep_all.py +366 -366
- powertrain_build/signal_incons_html_rep_base.py +168 -168
- powertrain_build/signal_inconsistency_check.py +641 -641
- powertrain_build/signal_interfaces.py +864 -864
- powertrain_build/templates/Index_SigCheck_All.html +22 -22
- powertrain_build/templates/Index_SigIf_All.html +19 -19
- powertrain_build/types.py +218 -218
- powertrain_build/unit_configs.py +419 -419
- powertrain_build/user_defined_types.py +660 -660
- powertrain_build/versioncheck.py +66 -66
- powertrain_build/wrapper.py +512 -512
- powertrain_build/xlrd_csv.py +87 -87
- powertrain_build/zone_controller/__init__.py +4 -4
- powertrain_build/zone_controller/calibration.py +176 -176
- powertrain_build/zone_controller/composition_yaml.py +880 -878
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/METADATA +100 -100
- powertrain_build-1.13.3.dev3.dist-info/RECORD +130 -0
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/WHEEL +1 -1
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/LICENSE +202 -202
- powertrain_build-1.13.3.dev3.dist-info/pbr.json +1 -0
- powertrain_build-1.13.1.dist-info/RECORD +0 -130
- powertrain_build-1.13.1.dist-info/pbr.json +0 -1
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/entry_points.txt +0 -0
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/AUTHORS +0 -0
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/NOTICE +0 -0
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/top_level.txt +0 -0
powertrain_build/pt_matlab.py
CHANGED
|
@@ -1,430 +1,430 @@
|
|
|
1
|
-
# Copyright 2024 Volvo Car Corporation
|
|
2
|
-
# Licensed under Apache 2.0.
|
|
3
|
-
|
|
4
|
-
"""Library for using matlab.
|
|
5
|
-
|
|
6
|
-
Called pt_matlab to not collide with Mathwork's matlab package.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import logging
|
|
10
|
-
import os
|
|
11
|
-
import platform
|
|
12
|
-
import subprocess
|
|
13
|
-
import sys
|
|
14
|
-
import time
|
|
15
|
-
|
|
16
|
-
my_system = platform.system()
|
|
17
|
-
if my_system == "Darwin":
|
|
18
|
-
MATLAB_BIN = os.environ.get(
|
|
19
|
-
"MatInstl",
|
|
20
|
-
os.environ.get("MatInstl2017", "/Applications/MATLAB_R2020a.app/bin/matlab")
|
|
21
|
-
)
|
|
22
|
-
elif my_system == "Linux":
|
|
23
|
-
MATLAB_BIN = os.environ.get("MatInstl", os.environ.get("MatInstl2017", "/usr/local/MATLAB/R2017b/bin/matlab"))
|
|
24
|
-
else:
|
|
25
|
-
from . import pt_win32
|
|
26
|
-
MATLAB_BIN = os.environ.get("MatInstl", os.environ.get("MatInstl2017", "C:\\MATLABR2017b_x64\\bin\\matlab.exe"))
|
|
27
|
-
|
|
28
|
-
LOGGER = logging.getLogger(__name__)
|
|
29
|
-
logging.basicConfig(level=logging.INFO)
|
|
30
|
-
RETRY_TIME = 10
|
|
31
|
-
POLL_TIME = 1
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def get_env():
|
|
35
|
-
"""Get environment variables."""
|
|
36
|
-
env = os.environ.copy()
|
|
37
|
-
workspace = os.environ.get("WORKSPACE", "")
|
|
38
|
-
if workspace:
|
|
39
|
-
env["MATLAB_PREFDIR"] = os.path.join(workspace, "ConfigDocuments", "buildSlaveMatlabSettings")
|
|
40
|
-
return env
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def wrap_command(command):
|
|
44
|
-
"""Wrap a command in a try-catch with an exit at the end.
|
|
45
|
-
|
|
46
|
-
Use this to be sure never to raise an error in matlab that causes a hanging matlab.
|
|
47
|
-
This is useful when writing matlab scripts to be used in jenkins jobs.
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
command (str): command to wrap in try-catch-exit.
|
|
51
|
-
Returns:
|
|
52
|
-
command (str): the command wrapped in try-catch-exit
|
|
53
|
-
"""
|
|
54
|
-
return f"try;exitcode=0;{command};catch err;disp(getReport(err));exitcode=1;end;exit(exitcode);"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def wrap_m_script(path_to_mscript):
|
|
58
|
-
"""Wrap a path to an m-script to ensure no jobs in jenkins are left hanging.
|
|
59
|
-
|
|
60
|
-
This is useful when writing matlab scripts to be used in jenkins jobs.
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
path_to_mscript (str): file to run in matlab.
|
|
64
|
-
Returns:
|
|
65
|
-
command (str): the command to send to matlab to run the script
|
|
66
|
-
"""
|
|
67
|
-
mscript_folder, mscript_file = os.path.split(path_to_mscript)
|
|
68
|
-
LOGGER.debug("Using folder %s for the m-script", mscript_folder)
|
|
69
|
-
if mscript_folder:
|
|
70
|
-
command = wrap_command(
|
|
71
|
-
f"addpath('{mscript_folder}');assert(boolean(exist('{mscript_file[:-2]}')));{mscript_file[:-2]}"
|
|
72
|
-
)
|
|
73
|
-
else:
|
|
74
|
-
command = wrap_command(f"assert(boolean(exist('{mscript_file[:-2]}')));{mscript_file[:-2]}")
|
|
75
|
-
return f'"{command}"'
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def list_to_cell(python_list):
|
|
79
|
-
"""Convert a python list to a cell argument string for matlab.
|
|
80
|
-
|
|
81
|
-
Args:
|
|
82
|
-
python_list (list): A list in python
|
|
83
|
-
Returns:
|
|
84
|
-
cell (str): A cell to be read by Matlab.
|
|
85
|
-
"""
|
|
86
|
-
matlab_list = "', '".join(python_list)
|
|
87
|
-
return f"{{'{matlab_list}'}}"
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def write_m_script(mfile, base_cmd, wrap_cmd=True):
|
|
91
|
-
"""Write an m-script to run from the command line.
|
|
92
|
-
|
|
93
|
-
The m-script will exit Matlab with an exitcode.
|
|
94
|
-
This exitcode can be set in the base_cmd if needed.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
mfile (str): path to m-file to create
|
|
98
|
-
base_cmd (str): Base command to run in Matlab
|
|
99
|
-
wrap_cmd (bool): wrap command
|
|
100
|
-
"""
|
|
101
|
-
if wrap_cmd:
|
|
102
|
-
cmd = wrap_command(base_cmd)
|
|
103
|
-
else:
|
|
104
|
-
cmd = base_cmd
|
|
105
|
-
LOGGER.debug("Writing %s", mfile)
|
|
106
|
-
with open(mfile, mode="w", encoding="utf-8") as m_file:
|
|
107
|
-
m_file.writelines(cmd.split(os.linesep))
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def is_model(model_file):
|
|
111
|
-
"""Check if a file name is a simulink model or not.
|
|
112
|
-
|
|
113
|
-
Args:
|
|
114
|
-
model_file (str): filename to check
|
|
115
|
-
"""
|
|
116
|
-
return model_file.endswith(".mdl") or model_file.endswith(".slx")
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def extract_model_names(files, ending):
|
|
120
|
-
"""Find model names from path to files.
|
|
121
|
-
|
|
122
|
-
Checks if the files corresponds to model files.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
files (list): files that potentially corresponds to models
|
|
126
|
-
ending (str): file ending that is used for models.
|
|
127
|
-
Returns:
|
|
128
|
-
found_models (list): Models to run checkscripts on.
|
|
129
|
-
"""
|
|
130
|
-
found_models = []
|
|
131
|
-
for found_file in files:
|
|
132
|
-
name = os.path.basename(found_file)
|
|
133
|
-
if name.endswith(ending):
|
|
134
|
-
name = name[: -len(ending)]
|
|
135
|
-
found_models.append(name)
|
|
136
|
-
return found_models
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def cmd_path(path, subdirs=False):
|
|
140
|
-
"""Generate Matlab command text for adding supplied path and subdirectories.
|
|
141
|
-
|
|
142
|
-
Args:
|
|
143
|
-
path (str): Path to generate commands for adding.
|
|
144
|
-
subdirs (bool): If True, recursively adds subdirs. Default: False
|
|
145
|
-
|
|
146
|
-
Returns:
|
|
147
|
-
cmds (list): Commands as a list of strings.
|
|
148
|
-
"""
|
|
149
|
-
return f"addpath(genpath('{path}'))" if subdirs else f"addpath('{path}')"
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def cmd_callfunc(func, *args):
|
|
153
|
-
"""Generate Matlab command for calling a function with optional arguments.
|
|
154
|
-
|
|
155
|
-
Example: cmd_callfunc('my_function')
|
|
156
|
-
cmd_callfunc('my_function', 'argument 1', 'argument 2')
|
|
157
|
-
|
|
158
|
-
Args:
|
|
159
|
-
func (str): Name of function to call.
|
|
160
|
-
args (argument list): Variadic argument list to pass to function.
|
|
161
|
-
Returns:
|
|
162
|
-
cmd (str): Command text for calling function.
|
|
163
|
-
"""
|
|
164
|
-
args = ", ".join([cmd_arg(arg) for arg in args])
|
|
165
|
-
return f"{func}({args})"
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def cmd_arg(arg):
|
|
169
|
-
"""Transform Python argument to Matlab argument."""
|
|
170
|
-
if isinstance(arg, str):
|
|
171
|
-
return f"'{arg}'"
|
|
172
|
-
if isinstance(arg, bool):
|
|
173
|
-
return "true" if arg else "false"
|
|
174
|
-
if isinstance(arg, list):
|
|
175
|
-
return list_to_cell(arg)
|
|
176
|
-
raise ValueError("Unable to convert argument to Matlab type")
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def cmds_join(cmds):
|
|
180
|
-
"""Join several Matlab command strings together, separated by terminators and newlines.
|
|
181
|
-
|
|
182
|
-
Args:
|
|
183
|
-
cmds (list): List of command string to join together.
|
|
184
|
-
Returns:
|
|
185
|
-
cmds (str): Command text for executing all supplied commands.
|
|
186
|
-
"""
|
|
187
|
-
return f";{os.linesep}".join(cmds)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
class TargetlinkWatcher(logging.StreamHandler):
|
|
191
|
-
"""Class to check output log for known error messages."""
|
|
192
|
-
|
|
193
|
-
intermittent_errors = [
|
|
194
|
-
"Undefined function 'tl_pref' for input arguments of type 'char'",
|
|
195
|
-
"Undefined function 'dsdd_manage_project' for input arguments of type 'char'.",
|
|
196
|
-
"Exception: ACCESS_VIOLATION",
|
|
197
|
-
]
|
|
198
|
-
|
|
199
|
-
def __init__(self, stream):
|
|
200
|
-
"""Init."""
|
|
201
|
-
super().__init__(stream)
|
|
202
|
-
self.targetlink_error = False
|
|
203
|
-
|
|
204
|
-
def reset_errors(self):
|
|
205
|
-
"""Reset errors."""
|
|
206
|
-
self.targetlink_error = False
|
|
207
|
-
|
|
208
|
-
def emit(self, record):
|
|
209
|
-
"""Set error flag on error message in log."""
|
|
210
|
-
for intermittent_error in self.intermittent_errors:
|
|
211
|
-
if intermittent_error in record.getMessage():
|
|
212
|
-
self.targetlink_error = True
|
|
213
|
-
super().emit(record)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
class MatlabWatcher(logging.StreamHandler):
|
|
217
|
-
"""Class to check output log for sucess message."""
|
|
218
|
-
|
|
219
|
-
success_message = "Matlab task succeeded!"
|
|
220
|
-
|
|
221
|
-
def __init__(self):
|
|
222
|
-
"""Init."""
|
|
223
|
-
super().__init__()
|
|
224
|
-
self.task_success = False
|
|
225
|
-
|
|
226
|
-
def reset_errors(self):
|
|
227
|
-
"""Reset errors."""
|
|
228
|
-
self.task_success = False
|
|
229
|
-
|
|
230
|
-
def emit(self, record):
|
|
231
|
-
"""Set success flag on success message in log."""
|
|
232
|
-
if self.success_message in record.getMessage():
|
|
233
|
-
self.task_success = True
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
class Matlab:
|
|
237
|
-
"""Wrapper for calling matlab from python scripts.
|
|
238
|
-
|
|
239
|
-
The wrapper runs a m-script and optionally pipes the logs to stdout.
|
|
240
|
-
"""
|
|
241
|
-
|
|
242
|
-
def __init__(self, log=None, dry_run=False, matlab_bin=MATLAB_BIN, nojvm=True):
|
|
243
|
-
"""Init.
|
|
244
|
-
|
|
245
|
-
Args:
|
|
246
|
-
log (str): file name to log to. Default: None
|
|
247
|
-
dry_run (bool): Run a mocked version of matlab. Default: False
|
|
248
|
-
matlab_bin (str): path to matlab binary to use in execution
|
|
249
|
-
nojvm (bool): False if jvm should be used. Default: True
|
|
250
|
-
"""
|
|
251
|
-
self.log = log
|
|
252
|
-
self.dry_run = dry_run
|
|
253
|
-
self.nojvm = nojvm
|
|
254
|
-
self.targetlink_watcher = TargetlinkWatcher(sys.stdout)
|
|
255
|
-
self.matlab_watcher = MatlabWatcher()
|
|
256
|
-
self.mlogger = self.create_logger()
|
|
257
|
-
self.mlogger.addHandler(self.matlab_watcher)
|
|
258
|
-
self.open_log_file = None
|
|
259
|
-
self.matlab_bin = matlab_bin
|
|
260
|
-
|
|
261
|
-
@staticmethod
|
|
262
|
-
def add_args(parser):
|
|
263
|
-
"""Parse arguments."""
|
|
264
|
-
matlab_parser = parser.add_argument_group("matlab arguments")
|
|
265
|
-
matlab_parser.add_argument(
|
|
266
|
-
"--matlab-bin",
|
|
267
|
-
help=f"Path to the matlab binary to use. Defaults to {MATLAB_BIN}.",
|
|
268
|
-
default=MATLAB_BIN,
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
def create_logger(self):
|
|
272
|
-
"""Create logger for matlab.
|
|
273
|
-
|
|
274
|
-
Returns:
|
|
275
|
-
logger (logging.Logger): Logger for matlab.
|
|
276
|
-
"""
|
|
277
|
-
handler = self.targetlink_watcher
|
|
278
|
-
handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(message)s"))
|
|
279
|
-
logger = logging.getLogger("matlab")
|
|
280
|
-
logger.addHandler(handler)
|
|
281
|
-
return logger
|
|
282
|
-
|
|
283
|
-
def sync_log(self, last_stat):
|
|
284
|
-
"""Read the latest loglines from the matlab log and write to log.
|
|
285
|
-
|
|
286
|
-
Checks if the matlab log file has been updated since last call and
|
|
287
|
-
redirects new entries to the python log.
|
|
288
|
-
If self.log is None, this function returns None at once.
|
|
289
|
-
|
|
290
|
-
Args:
|
|
291
|
-
last_stat (os.stat): last timestamp for an updated log.
|
|
292
|
-
Returns:
|
|
293
|
-
new_stat (os.stat): new timestamp for an updated log.
|
|
294
|
-
"""
|
|
295
|
-
if self.open_log_file is None:
|
|
296
|
-
return None
|
|
297
|
-
current_position = self.open_log_file.tell()
|
|
298
|
-
if os.stat(self.open_log_file.name) == last_stat:
|
|
299
|
-
return last_stat # Do not read partial lines again.
|
|
300
|
-
for line in self.open_log_file.readlines():
|
|
301
|
-
# The non-finished lines behaviour was sometimes seen when using diary.
|
|
302
|
-
# It should not hurt to have, but if it is causing problems, just remove it.
|
|
303
|
-
# It is purely cosmetic.
|
|
304
|
-
if line.endswith("\n"):
|
|
305
|
-
if line != "\n": # Skip the end of the line we jumped to from a non-finished line
|
|
306
|
-
self.mlogger.info(line[0:-1])
|
|
307
|
-
else:
|
|
308
|
-
LOGGER.debug("Partial line read. Go back and wait for update.")
|
|
309
|
-
self.open_log_file.seek(current_position - len(line))
|
|
310
|
-
break
|
|
311
|
-
current_position = self.open_log_file.tell()
|
|
312
|
-
return os.stat(self.open_log_file.name)
|
|
313
|
-
|
|
314
|
-
def compose_m_script_cmd(self, mfile, wrap_cmd):
|
|
315
|
-
"""Compose batch script to run matlab."""
|
|
316
|
-
|
|
317
|
-
if my_system in {"Darwin", "Linux"}:
|
|
318
|
-
cmd = [
|
|
319
|
-
self.matlab_bin if not self.dry_run else "echo",
|
|
320
|
-
"-nodisplay",
|
|
321
|
-
"-nodesktop",
|
|
322
|
-
]
|
|
323
|
-
else:
|
|
324
|
-
cmd = [
|
|
325
|
-
self.matlab_bin if not self.dry_run else "cmd rem",
|
|
326
|
-
"-nodisplay",
|
|
327
|
-
"-nodesktop",
|
|
328
|
-
"-wait",
|
|
329
|
-
"-minimize",
|
|
330
|
-
]
|
|
331
|
-
|
|
332
|
-
if self.log:
|
|
333
|
-
cmd += ["-logfile", self.log]
|
|
334
|
-
if self.nojvm:
|
|
335
|
-
cmd += ["-nojvm"]
|
|
336
|
-
if wrap_cmd:
|
|
337
|
-
if my_system == "Darwin":
|
|
338
|
-
cmd += ["<", wrap_m_script(mfile)]
|
|
339
|
-
else:
|
|
340
|
-
cmd += ["-r", wrap_m_script(mfile)]
|
|
341
|
-
else:
|
|
342
|
-
if my_system == "Darwin":
|
|
343
|
-
cmd += ["<", mfile]
|
|
344
|
-
else: # Linux will be similar to Windows
|
|
345
|
-
cmd += ["-r", mfile[:-2]]
|
|
346
|
-
|
|
347
|
-
return cmd
|
|
348
|
-
|
|
349
|
-
def run_m_script(self, mfile, wrap_cmd=True, attempts=10):
|
|
350
|
-
"""Run the composed m-script.
|
|
351
|
-
|
|
352
|
-
Args:
|
|
353
|
-
mfile (str): The composed m-script
|
|
354
|
-
wrap_cmd (bool): wrap command (Default: True)
|
|
355
|
-
attempts (int): Number of times to run the command again if an intermittent problem
|
|
356
|
-
is detected (Default: 1)
|
|
357
|
-
Returns:
|
|
358
|
-
process_status: Exitcode from Matlab (or 1 if Matlab was never started)
|
|
359
|
-
"""
|
|
360
|
-
|
|
361
|
-
def wait_for_process(p_matlab, stat=None):
|
|
362
|
-
stat = self.sync_log(None) # Initial sync in case matlab has already finished
|
|
363
|
-
|
|
364
|
-
if my_system in {"Darwin", "Linux"}:
|
|
365
|
-
while p_matlab.poll() is None:
|
|
366
|
-
time.sleep(POLL_TIME)
|
|
367
|
-
stat = self.sync_log(stat)
|
|
368
|
-
else:
|
|
369
|
-
while p_matlab.poll() == pt_win32.STILL_ACTIVE:
|
|
370
|
-
time.sleep(POLL_TIME)
|
|
371
|
-
stat = self.sync_log(stat)
|
|
372
|
-
|
|
373
|
-
if attempts <= 0:
|
|
374
|
-
LOGGER.error("Already attempted this too many times. Exiting.")
|
|
375
|
-
return 2 # Do not trust previous exit codes
|
|
376
|
-
|
|
377
|
-
if self.log and os.path.isfile(self.log):
|
|
378
|
-
os.remove(self.log)
|
|
379
|
-
|
|
380
|
-
p_matlab = None
|
|
381
|
-
process_status = 1 # Something could go wrong in the try, before the exitcode is set
|
|
382
|
-
|
|
383
|
-
try:
|
|
384
|
-
env = get_env()
|
|
385
|
-
LOGGER.debug("Running in dry_run mode: %s", self.dry_run)
|
|
386
|
-
cmd = self.compose_m_script_cmd(mfile, wrap_cmd)
|
|
387
|
-
LOGGER.debug("Calling: %s", " ".join(cmd))
|
|
388
|
-
|
|
389
|
-
if my_system in {"Darwin", "Linux"}:
|
|
390
|
-
p_matlab = subprocess.Popen(cmd)
|
|
391
|
-
else:
|
|
392
|
-
p_matlab = pt_win32.PtWin32Process()
|
|
393
|
-
p_matlab.set_environment(env)
|
|
394
|
-
p_matlab.run(command=" ".join(cmd))
|
|
395
|
-
|
|
396
|
-
if not self.dry_run:
|
|
397
|
-
if self.log:
|
|
398
|
-
# Wait for log file to be created
|
|
399
|
-
if my_system in {"Darwin", "Linux"}:
|
|
400
|
-
while not os.path.isfile(self.log) and p_matlab.poll() is None:
|
|
401
|
-
time.sleep(POLL_TIME)
|
|
402
|
-
else:
|
|
403
|
-
while not os.path.isfile(self.log) and p_matlab.poll() == pt_win32.STILL_ACTIVE:
|
|
404
|
-
time.sleep(POLL_TIME)
|
|
405
|
-
|
|
406
|
-
self.open_log_file = open(self.log, "r", encoding="utf-8", errors='ignore') if self.log else None
|
|
407
|
-
|
|
408
|
-
wait_for_process(p_matlab)
|
|
409
|
-
process_status = p_matlab.poll()
|
|
410
|
-
|
|
411
|
-
LOGGER.info("Matlab returned with exitcode %s", process_status)
|
|
412
|
-
if self.targetlink_watcher.targetlink_error:
|
|
413
|
-
LOGGER.warning("Found an intermittent targetlink problem. Trying again, attempts left: %s", attempts)
|
|
414
|
-
if self.open_log_file:
|
|
415
|
-
self.open_log_file.close()
|
|
416
|
-
self.targetlink_watcher.reset_errors()
|
|
417
|
-
time.sleep(RETRY_TIME)
|
|
418
|
-
process_status = self.run_m_script(mfile, wrap_cmd=wrap_cmd, attempts=attempts - 1)
|
|
419
|
-
finally:
|
|
420
|
-
if my_system in {"Darwin", "Linux"}:
|
|
421
|
-
if p_matlab.poll() is not None:
|
|
422
|
-
p_matlab.terminate()
|
|
423
|
-
p_matlab.wait()
|
|
424
|
-
else:
|
|
425
|
-
if p_matlab.poll() == pt_win32.STILL_ACTIVE:
|
|
426
|
-
p_matlab.terminate()
|
|
427
|
-
|
|
428
|
-
if self.open_log_file:
|
|
429
|
-
self.open_log_file.close()
|
|
430
|
-
return process_status
|
|
1
|
+
# Copyright 2024 Volvo Car Corporation
|
|
2
|
+
# Licensed under Apache 2.0.
|
|
3
|
+
|
|
4
|
+
"""Library for using matlab.
|
|
5
|
+
|
|
6
|
+
Called pt_matlab to not collide with Mathwork's matlab package.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import platform
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import time
|
|
15
|
+
|
|
16
|
+
my_system = platform.system()
|
|
17
|
+
if my_system == "Darwin":
|
|
18
|
+
MATLAB_BIN = os.environ.get(
|
|
19
|
+
"MatInstl",
|
|
20
|
+
os.environ.get("MatInstl2017", "/Applications/MATLAB_R2020a.app/bin/matlab")
|
|
21
|
+
)
|
|
22
|
+
elif my_system == "Linux":
|
|
23
|
+
MATLAB_BIN = os.environ.get("MatInstl", os.environ.get("MatInstl2017", "/usr/local/MATLAB/R2017b/bin/matlab"))
|
|
24
|
+
else:
|
|
25
|
+
from . import pt_win32
|
|
26
|
+
MATLAB_BIN = os.environ.get("MatInstl", os.environ.get("MatInstl2017", "C:\\MATLABR2017b_x64\\bin\\matlab.exe"))
|
|
27
|
+
|
|
28
|
+
LOGGER = logging.getLogger(__name__)
|
|
29
|
+
logging.basicConfig(level=logging.INFO)
|
|
30
|
+
RETRY_TIME = 10
|
|
31
|
+
POLL_TIME = 1
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_env():
|
|
35
|
+
"""Get environment variables."""
|
|
36
|
+
env = os.environ.copy()
|
|
37
|
+
workspace = os.environ.get("WORKSPACE", "")
|
|
38
|
+
if workspace:
|
|
39
|
+
env["MATLAB_PREFDIR"] = os.path.join(workspace, "ConfigDocuments", "buildSlaveMatlabSettings")
|
|
40
|
+
return env
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def wrap_command(command):
|
|
44
|
+
"""Wrap a command in a try-catch with an exit at the end.
|
|
45
|
+
|
|
46
|
+
Use this to be sure never to raise an error in matlab that causes a hanging matlab.
|
|
47
|
+
This is useful when writing matlab scripts to be used in jenkins jobs.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
command (str): command to wrap in try-catch-exit.
|
|
51
|
+
Returns:
|
|
52
|
+
command (str): the command wrapped in try-catch-exit
|
|
53
|
+
"""
|
|
54
|
+
return f"try;exitcode=0;{command};catch err;disp(getReport(err));exitcode=1;end;exit(exitcode);"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def wrap_m_script(path_to_mscript):
|
|
58
|
+
"""Wrap a path to an m-script to ensure no jobs in jenkins are left hanging.
|
|
59
|
+
|
|
60
|
+
This is useful when writing matlab scripts to be used in jenkins jobs.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
path_to_mscript (str): file to run in matlab.
|
|
64
|
+
Returns:
|
|
65
|
+
command (str): the command to send to matlab to run the script
|
|
66
|
+
"""
|
|
67
|
+
mscript_folder, mscript_file = os.path.split(path_to_mscript)
|
|
68
|
+
LOGGER.debug("Using folder %s for the m-script", mscript_folder)
|
|
69
|
+
if mscript_folder:
|
|
70
|
+
command = wrap_command(
|
|
71
|
+
f"addpath('{mscript_folder}');assert(boolean(exist('{mscript_file[:-2]}')));{mscript_file[:-2]}"
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
command = wrap_command(f"assert(boolean(exist('{mscript_file[:-2]}')));{mscript_file[:-2]}")
|
|
75
|
+
return f'"{command}"'
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def list_to_cell(python_list):
|
|
79
|
+
"""Convert a python list to a cell argument string for matlab.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
python_list (list): A list in python
|
|
83
|
+
Returns:
|
|
84
|
+
cell (str): A cell to be read by Matlab.
|
|
85
|
+
"""
|
|
86
|
+
matlab_list = "', '".join(python_list)
|
|
87
|
+
return f"{{'{matlab_list}'}}"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def write_m_script(mfile, base_cmd, wrap_cmd=True):
|
|
91
|
+
"""Write an m-script to run from the command line.
|
|
92
|
+
|
|
93
|
+
The m-script will exit Matlab with an exitcode.
|
|
94
|
+
This exitcode can be set in the base_cmd if needed.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
mfile (str): path to m-file to create
|
|
98
|
+
base_cmd (str): Base command to run in Matlab
|
|
99
|
+
wrap_cmd (bool): wrap command
|
|
100
|
+
"""
|
|
101
|
+
if wrap_cmd:
|
|
102
|
+
cmd = wrap_command(base_cmd)
|
|
103
|
+
else:
|
|
104
|
+
cmd = base_cmd
|
|
105
|
+
LOGGER.debug("Writing %s", mfile)
|
|
106
|
+
with open(mfile, mode="w", encoding="utf-8") as m_file:
|
|
107
|
+
m_file.writelines(cmd.split(os.linesep))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def is_model(model_file):
|
|
111
|
+
"""Check if a file name is a simulink model or not.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
model_file (str): filename to check
|
|
115
|
+
"""
|
|
116
|
+
return model_file.endswith(".mdl") or model_file.endswith(".slx")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def extract_model_names(files, ending):
|
|
120
|
+
"""Find model names from path to files.
|
|
121
|
+
|
|
122
|
+
Checks if the files corresponds to model files.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
files (list): files that potentially corresponds to models
|
|
126
|
+
ending (str): file ending that is used for models.
|
|
127
|
+
Returns:
|
|
128
|
+
found_models (list): Models to run checkscripts on.
|
|
129
|
+
"""
|
|
130
|
+
found_models = []
|
|
131
|
+
for found_file in files:
|
|
132
|
+
name = os.path.basename(found_file)
|
|
133
|
+
if name.endswith(ending):
|
|
134
|
+
name = name[: -len(ending)]
|
|
135
|
+
found_models.append(name)
|
|
136
|
+
return found_models
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def cmd_path(path, subdirs=False):
|
|
140
|
+
"""Generate Matlab command text for adding supplied path and subdirectories.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
path (str): Path to generate commands for adding.
|
|
144
|
+
subdirs (bool): If True, recursively adds subdirs. Default: False
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
cmds (list): Commands as a list of strings.
|
|
148
|
+
"""
|
|
149
|
+
return f"addpath(genpath('{path}'))" if subdirs else f"addpath('{path}')"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def cmd_callfunc(func, *args):
|
|
153
|
+
"""Generate Matlab command for calling a function with optional arguments.
|
|
154
|
+
|
|
155
|
+
Example: cmd_callfunc('my_function')
|
|
156
|
+
cmd_callfunc('my_function', 'argument 1', 'argument 2')
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
func (str): Name of function to call.
|
|
160
|
+
args (argument list): Variadic argument list to pass to function.
|
|
161
|
+
Returns:
|
|
162
|
+
cmd (str): Command text for calling function.
|
|
163
|
+
"""
|
|
164
|
+
args = ", ".join([cmd_arg(arg) for arg in args])
|
|
165
|
+
return f"{func}({args})"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def cmd_arg(arg):
|
|
169
|
+
"""Transform Python argument to Matlab argument."""
|
|
170
|
+
if isinstance(arg, str):
|
|
171
|
+
return f"'{arg}'"
|
|
172
|
+
if isinstance(arg, bool):
|
|
173
|
+
return "true" if arg else "false"
|
|
174
|
+
if isinstance(arg, list):
|
|
175
|
+
return list_to_cell(arg)
|
|
176
|
+
raise ValueError("Unable to convert argument to Matlab type")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def cmds_join(cmds):
|
|
180
|
+
"""Join several Matlab command strings together, separated by terminators and newlines.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
cmds (list): List of command string to join together.
|
|
184
|
+
Returns:
|
|
185
|
+
cmds (str): Command text for executing all supplied commands.
|
|
186
|
+
"""
|
|
187
|
+
return f";{os.linesep}".join(cmds)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class TargetlinkWatcher(logging.StreamHandler):
|
|
191
|
+
"""Class to check output log for known error messages."""
|
|
192
|
+
|
|
193
|
+
intermittent_errors = [
|
|
194
|
+
"Undefined function 'tl_pref' for input arguments of type 'char'",
|
|
195
|
+
"Undefined function 'dsdd_manage_project' for input arguments of type 'char'.",
|
|
196
|
+
"Exception: ACCESS_VIOLATION",
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
def __init__(self, stream):
|
|
200
|
+
"""Init."""
|
|
201
|
+
super().__init__(stream)
|
|
202
|
+
self.targetlink_error = False
|
|
203
|
+
|
|
204
|
+
def reset_errors(self):
|
|
205
|
+
"""Reset errors."""
|
|
206
|
+
self.targetlink_error = False
|
|
207
|
+
|
|
208
|
+
def emit(self, record):
|
|
209
|
+
"""Set error flag on error message in log."""
|
|
210
|
+
for intermittent_error in self.intermittent_errors:
|
|
211
|
+
if intermittent_error in record.getMessage():
|
|
212
|
+
self.targetlink_error = True
|
|
213
|
+
super().emit(record)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class MatlabWatcher(logging.StreamHandler):
|
|
217
|
+
"""Class to check output log for sucess message."""
|
|
218
|
+
|
|
219
|
+
success_message = "Matlab task succeeded!"
|
|
220
|
+
|
|
221
|
+
def __init__(self):
|
|
222
|
+
"""Init."""
|
|
223
|
+
super().__init__()
|
|
224
|
+
self.task_success = False
|
|
225
|
+
|
|
226
|
+
def reset_errors(self):
|
|
227
|
+
"""Reset errors."""
|
|
228
|
+
self.task_success = False
|
|
229
|
+
|
|
230
|
+
def emit(self, record):
|
|
231
|
+
"""Set success flag on success message in log."""
|
|
232
|
+
if self.success_message in record.getMessage():
|
|
233
|
+
self.task_success = True
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class Matlab:
|
|
237
|
+
"""Wrapper for calling matlab from python scripts.
|
|
238
|
+
|
|
239
|
+
The wrapper runs a m-script and optionally pipes the logs to stdout.
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
def __init__(self, log=None, dry_run=False, matlab_bin=MATLAB_BIN, nojvm=True):
|
|
243
|
+
"""Init.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
log (str): file name to log to. Default: None
|
|
247
|
+
dry_run (bool): Run a mocked version of matlab. Default: False
|
|
248
|
+
matlab_bin (str): path to matlab binary to use in execution
|
|
249
|
+
nojvm (bool): False if jvm should be used. Default: True
|
|
250
|
+
"""
|
|
251
|
+
self.log = log
|
|
252
|
+
self.dry_run = dry_run
|
|
253
|
+
self.nojvm = nojvm
|
|
254
|
+
self.targetlink_watcher = TargetlinkWatcher(sys.stdout)
|
|
255
|
+
self.matlab_watcher = MatlabWatcher()
|
|
256
|
+
self.mlogger = self.create_logger()
|
|
257
|
+
self.mlogger.addHandler(self.matlab_watcher)
|
|
258
|
+
self.open_log_file = None
|
|
259
|
+
self.matlab_bin = matlab_bin
|
|
260
|
+
|
|
261
|
+
@staticmethod
|
|
262
|
+
def add_args(parser):
|
|
263
|
+
"""Parse arguments."""
|
|
264
|
+
matlab_parser = parser.add_argument_group("matlab arguments")
|
|
265
|
+
matlab_parser.add_argument(
|
|
266
|
+
"--matlab-bin",
|
|
267
|
+
help=f"Path to the matlab binary to use. Defaults to {MATLAB_BIN}.",
|
|
268
|
+
default=MATLAB_BIN,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def create_logger(self):
|
|
272
|
+
"""Create logger for matlab.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
logger (logging.Logger): Logger for matlab.
|
|
276
|
+
"""
|
|
277
|
+
handler = self.targetlink_watcher
|
|
278
|
+
handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(message)s"))
|
|
279
|
+
logger = logging.getLogger("matlab")
|
|
280
|
+
logger.addHandler(handler)
|
|
281
|
+
return logger
|
|
282
|
+
|
|
283
|
+
def sync_log(self, last_stat):
|
|
284
|
+
"""Read the latest loglines from the matlab log and write to log.
|
|
285
|
+
|
|
286
|
+
Checks if the matlab log file has been updated since last call and
|
|
287
|
+
redirects new entries to the python log.
|
|
288
|
+
If self.log is None, this function returns None at once.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
last_stat (os.stat): last timestamp for an updated log.
|
|
292
|
+
Returns:
|
|
293
|
+
new_stat (os.stat): new timestamp for an updated log.
|
|
294
|
+
"""
|
|
295
|
+
if self.open_log_file is None:
|
|
296
|
+
return None
|
|
297
|
+
current_position = self.open_log_file.tell()
|
|
298
|
+
if os.stat(self.open_log_file.name) == last_stat:
|
|
299
|
+
return last_stat # Do not read partial lines again.
|
|
300
|
+
for line in self.open_log_file.readlines():
|
|
301
|
+
# The non-finished lines behaviour was sometimes seen when using diary.
|
|
302
|
+
# It should not hurt to have, but if it is causing problems, just remove it.
|
|
303
|
+
# It is purely cosmetic.
|
|
304
|
+
if line.endswith("\n"):
|
|
305
|
+
if line != "\n": # Skip the end of the line we jumped to from a non-finished line
|
|
306
|
+
self.mlogger.info(line[0:-1])
|
|
307
|
+
else:
|
|
308
|
+
LOGGER.debug("Partial line read. Go back and wait for update.")
|
|
309
|
+
self.open_log_file.seek(current_position - len(line))
|
|
310
|
+
break
|
|
311
|
+
current_position = self.open_log_file.tell()
|
|
312
|
+
return os.stat(self.open_log_file.name)
|
|
313
|
+
|
|
314
|
+
def compose_m_script_cmd(self, mfile, wrap_cmd):
|
|
315
|
+
"""Compose batch script to run matlab."""
|
|
316
|
+
|
|
317
|
+
if my_system in {"Darwin", "Linux"}:
|
|
318
|
+
cmd = [
|
|
319
|
+
self.matlab_bin if not self.dry_run else "echo",
|
|
320
|
+
"-nodisplay",
|
|
321
|
+
"-nodesktop",
|
|
322
|
+
]
|
|
323
|
+
else:
|
|
324
|
+
cmd = [
|
|
325
|
+
self.matlab_bin if not self.dry_run else "cmd rem",
|
|
326
|
+
"-nodisplay",
|
|
327
|
+
"-nodesktop",
|
|
328
|
+
"-wait",
|
|
329
|
+
"-minimize",
|
|
330
|
+
]
|
|
331
|
+
|
|
332
|
+
if self.log:
|
|
333
|
+
cmd += ["-logfile", self.log]
|
|
334
|
+
if self.nojvm:
|
|
335
|
+
cmd += ["-nojvm"]
|
|
336
|
+
if wrap_cmd:
|
|
337
|
+
if my_system == "Darwin":
|
|
338
|
+
cmd += ["<", wrap_m_script(mfile)]
|
|
339
|
+
else:
|
|
340
|
+
cmd += ["-r", wrap_m_script(mfile)]
|
|
341
|
+
else:
|
|
342
|
+
if my_system == "Darwin":
|
|
343
|
+
cmd += ["<", mfile]
|
|
344
|
+
else: # Linux will be similar to Windows
|
|
345
|
+
cmd += ["-r", mfile[:-2]]
|
|
346
|
+
|
|
347
|
+
return cmd
|
|
348
|
+
|
|
349
|
+
def run_m_script(self, mfile, wrap_cmd=True, attempts=10):
|
|
350
|
+
"""Run the composed m-script.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
mfile (str): The composed m-script
|
|
354
|
+
wrap_cmd (bool): wrap command (Default: True)
|
|
355
|
+
attempts (int): Number of times to run the command again if an intermittent problem
|
|
356
|
+
is detected (Default: 1)
|
|
357
|
+
Returns:
|
|
358
|
+
process_status: Exitcode from Matlab (or 1 if Matlab was never started)
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
def wait_for_process(p_matlab, stat=None):
|
|
362
|
+
stat = self.sync_log(None) # Initial sync in case matlab has already finished
|
|
363
|
+
|
|
364
|
+
if my_system in {"Darwin", "Linux"}:
|
|
365
|
+
while p_matlab.poll() is None:
|
|
366
|
+
time.sleep(POLL_TIME)
|
|
367
|
+
stat = self.sync_log(stat)
|
|
368
|
+
else:
|
|
369
|
+
while p_matlab.poll() == pt_win32.STILL_ACTIVE:
|
|
370
|
+
time.sleep(POLL_TIME)
|
|
371
|
+
stat = self.sync_log(stat)
|
|
372
|
+
|
|
373
|
+
if attempts <= 0:
|
|
374
|
+
LOGGER.error("Already attempted this too many times. Exiting.")
|
|
375
|
+
return 2 # Do not trust previous exit codes
|
|
376
|
+
|
|
377
|
+
if self.log and os.path.isfile(self.log):
|
|
378
|
+
os.remove(self.log)
|
|
379
|
+
|
|
380
|
+
p_matlab = None
|
|
381
|
+
process_status = 1 # Something could go wrong in the try, before the exitcode is set
|
|
382
|
+
|
|
383
|
+
try:
|
|
384
|
+
env = get_env()
|
|
385
|
+
LOGGER.debug("Running in dry_run mode: %s", self.dry_run)
|
|
386
|
+
cmd = self.compose_m_script_cmd(mfile, wrap_cmd)
|
|
387
|
+
LOGGER.debug("Calling: %s", " ".join(cmd))
|
|
388
|
+
|
|
389
|
+
if my_system in {"Darwin", "Linux"}:
|
|
390
|
+
p_matlab = subprocess.Popen(cmd)
|
|
391
|
+
else:
|
|
392
|
+
p_matlab = pt_win32.PtWin32Process()
|
|
393
|
+
p_matlab.set_environment(env)
|
|
394
|
+
p_matlab.run(command=" ".join(cmd))
|
|
395
|
+
|
|
396
|
+
if not self.dry_run:
|
|
397
|
+
if self.log:
|
|
398
|
+
# Wait for log file to be created
|
|
399
|
+
if my_system in {"Darwin", "Linux"}:
|
|
400
|
+
while not os.path.isfile(self.log) and p_matlab.poll() is None:
|
|
401
|
+
time.sleep(POLL_TIME)
|
|
402
|
+
else:
|
|
403
|
+
while not os.path.isfile(self.log) and p_matlab.poll() == pt_win32.STILL_ACTIVE:
|
|
404
|
+
time.sleep(POLL_TIME)
|
|
405
|
+
|
|
406
|
+
self.open_log_file = open(self.log, "r", encoding="utf-8", errors='ignore') if self.log else None
|
|
407
|
+
|
|
408
|
+
wait_for_process(p_matlab)
|
|
409
|
+
process_status = p_matlab.poll()
|
|
410
|
+
|
|
411
|
+
LOGGER.info("Matlab returned with exitcode %s", process_status)
|
|
412
|
+
if self.targetlink_watcher.targetlink_error:
|
|
413
|
+
LOGGER.warning("Found an intermittent targetlink problem. Trying again, attempts left: %s", attempts)
|
|
414
|
+
if self.open_log_file:
|
|
415
|
+
self.open_log_file.close()
|
|
416
|
+
self.targetlink_watcher.reset_errors()
|
|
417
|
+
time.sleep(RETRY_TIME)
|
|
418
|
+
process_status = self.run_m_script(mfile, wrap_cmd=wrap_cmd, attempts=attempts - 1)
|
|
419
|
+
finally:
|
|
420
|
+
if my_system in {"Darwin", "Linux"}:
|
|
421
|
+
if p_matlab.poll() is not None:
|
|
422
|
+
p_matlab.terminate()
|
|
423
|
+
p_matlab.wait()
|
|
424
|
+
else:
|
|
425
|
+
if p_matlab.poll() == pt_win32.STILL_ACTIVE:
|
|
426
|
+
p_matlab.terminate()
|
|
427
|
+
|
|
428
|
+
if self.open_log_file:
|
|
429
|
+
self.open_log_file.close()
|
|
430
|
+
return process_status
|