@tsplc/build 0.0.0-stage → 0.1.0

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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -2
  3. package/dist/cli.js +1652 -0
  4. package/dist/cli.js.map +34 -0
  5. package/dist/index.js +1552 -0
  6. package/dist/index.js.map +31 -0
  7. package/dist/libraries.js +128 -0
  8. package/dist/libraries.js.map +10 -0
  9. package/docker/Dockerfile +173 -0
  10. package/docker/Dockerfile.dockerignore +17 -0
  11. package/docker/Dockerfile.source +15 -0
  12. package/docker/build-image +38 -0
  13. package/docker/codesys-build +5 -0
  14. package/docker/config.ts +8 -0
  15. package/docker/control-linux-sl/Dockerfile +38 -0
  16. package/docker/entrypoint.sh +10 -0
  17. package/docker/install/NOTICE.md +21 -0
  18. package/docker/install/core.sh +36 -0
  19. package/docker/install/decode.c +12 -0
  20. package/docker/install/device/Runtime.ico +0 -0
  21. package/docker/install/device/Runtime.png +0 -0
  22. package/docker/install/device/codesyscontrol.devdesc.xml +1502 -0
  23. package/docker/install/extract.py +43 -0
  24. package/docker/install/helper-launcher.c +46 -0
  25. package/docker/install/install.sh +94 -0
  26. package/docker/install/prerequisites.sh +15 -0
  27. package/docker/install/verify.py +133 -0
  28. package/docker/install/verify.sh +34 -0
  29. package/docker/install/wine-ncrypt/COPYING.LIB +502 -0
  30. package/docker/install/wine-ncrypt/README.md +28 -0
  31. package/docker/install/wine-ncrypt/enable-oaep.py +34 -0
  32. package/docker/install/wine-ncrypt/main.c +619 -0
  33. package/docker/install/wine-ncrypt/oaep.patch +11 -0
  34. package/docker/install/wine-signal/README.md +22 -0
  35. package/docker/install/wine-signal/build.sh +10 -0
  36. package/docker/install/wine-signal/check.sh +34 -0
  37. package/docker/install/wine-signal/noop-i386.c +2 -0
  38. package/docker/install/wine-signal/signal-state.c +105 -0
  39. package/docker/install/wine-signal/tests/avx-registers.c +53 -0
  40. package/docker/install/wine-signal/tests/frame-and-handler.c +192 -0
  41. package/docker/install/wine-signal/tests/loader-i386.c +8 -0
  42. package/docker/install/wine-signal/tests/signal-return.c +47 -0
  43. package/docker/install/wine-signal/wine +4 -0
  44. package/docker/runner.ts +131 -0
  45. package/package.json +53 -4
  46. package/python/build.py +655 -0
  47. package/python/codesys_online.py +300 -0
  48. package/python/test_build.py +444 -0
  49. package/python/test_codesys_online.py +170 -0
  50. package/python/test_installer_packages.py +216 -0
  51. package/python/test_installer_verification.py +307 -0
  52. package/python/test_worker.py +142 -0
  53. package/python/worker.py +89 -0
  54. package/runtime/install.sh +74 -0
  55. package/src/__tests__/fixtures/xml/fb-simple.xml +40 -0
  56. package/src/__tests__/fixtures/xml/fb-state-machine.xml +86 -0
  57. package/src/__tests__/fixtures/xml/prg-cyclic.xml +44 -0
  58. package/src/artifacts.ts +77 -0
  59. package/src/build.ts +87 -0
  60. package/src/cli.ts +116 -0
  61. package/src/config.ts +12 -0
  62. package/src/controller-online.ts +143 -0
  63. package/src/controller-session.ts +229 -0
  64. package/src/diagnostics.ts +66 -0
  65. package/src/docker-build.ts +148 -0
  66. package/src/docker-context.ts +35 -0
  67. package/src/docker-run.ts +104 -0
  68. package/src/host/codesys-command.ts +45 -0
  69. package/src/host/codesys-host.ts +145 -0
  70. package/src/host/codesys-worker.ts +345 -0
  71. package/src/host/windows-path.ts +73 -0
  72. package/src/index.ts +26 -0
  73. package/src/io.ts +127 -0
  74. package/src/libraries.ts +166 -0
  75. package/src/package-path.ts +5 -0
  76. package/src/run.ts +133 -0
  77. package/src/runtime-status.ts +21 -0
  78. package/src/sources.ts +248 -0
  79. package/src/st/declarations.ts +229 -0
  80. package/src/st/lexer.ts +125 -0
  81. package/src/st/parse.ts +202 -0
  82. package/src/st/reader.ts +50 -0
@@ -0,0 +1,300 @@
1
+ """CODESYS 3.5.22.30 online operations; executed by its IronPython engine."""
2
+ from __future__ import print_function
3
+
4
+ import io
5
+ import json
6
+ import os
7
+ import time
8
+ from build import apply_controller, fail_on_errors, import_objects, unique_child
9
+ from System import Activator, AppDomain, Array, Enum, Environment, Object
10
+ from System.Net import Dns, IPAddress
11
+ from System.Reflection import BindingFlags, TargetInvocationException
12
+
13
+
14
+ def core_application(online_application):
15
+ # The shipped scripting wrapper omits Keep from its login switch. Its core
16
+ # application provides a session-only login, which never compiles/downloads.
17
+ # This narrow bridge is pinned to the scripting version in our Docker image.
18
+ kind = online_application.GetType()
19
+ if kind.FullName != '_3S.CoDeSys.ScriptDriverOnline.OnlineApplication' or str(kind.Assembly.GetName().Version) != '4.2.0.0':
20
+ raise RuntimeError('Unsupported CODESYS online scripting implementation')
21
+ prop = kind.GetProperty('Application', BindingFlags.Instance | BindingFlags.NonPublic)
22
+ if prop is None:
23
+ raise RuntimeError('CODESYS session-only application API is unavailable')
24
+ return prop.GetValue(online_application, None)
25
+
26
+
27
+ def loaded_type(name):
28
+ for assembly in AppDomain.CurrentDomain.GetAssemblies():
29
+ kind = assembly.GetType(name, False)
30
+ if kind is not None:
31
+ return kind
32
+ raise RuntimeError('CODESYS API type is unavailable: ' + name)
33
+
34
+
35
+ def session_login(core):
36
+ kind = loaded_type('_3S.CoDeSys.OnlineManager.OnlineApplicationLoginConfiguration')
37
+ config = Activator.CreateInstance(kind)
38
+ mode = loaded_type('_3S.CoDeSys.Core.Online.ApplicationLoginClientModes')
39
+ kind.GetProperty('ClientModes').SetValue(config, Enum.Parse(mode, 'ReadOnly'), None)
40
+ api = loaded_type('_3S.CoDeSys.Core.Online.IOnlineApplication23')
41
+ login = api.GetMethod('Login')
42
+ if not login.Invoke(core, Array[Object]([True, config])):
43
+ raise RuntimeError('The controller does not contain this application')
44
+
45
+
46
+ def controller_ids(core):
47
+ ids = {'codeId': str(core.CodeId), 'dataId': str(core.DataId)}
48
+ if any(value == '00000000-0000-0000-0000-000000000000' for value in ids.values()):
49
+ raise RuntimeError('Controller application has no downloaded code/data identity')
50
+ return ids
51
+
52
+
53
+ def controller_state(core):
54
+ # Login updates IDs synchronously, but ApplicationState is timer-owned.
55
+ # Query the original device's application-list service for a fresh state.
56
+ name_property = core.GetType().GetProperty('ApplicationName', BindingFlags.Instance | BindingFlags.NonPublic)
57
+ name = str(name_property.GetValue(core, None))
58
+ api = loaded_type('_3S.CoDeSys.Core.Online.IOnlineDevice6')
59
+ arguments = Array[Object]([None, None])
60
+ api.GetMethod('ReadApplicationList').Invoke(core.OnlineDevice, arguments)
61
+ names, states = arguments[0], arguments[1]
62
+ if names is None or states is None or len(names) != len(states):
63
+ raise RuntimeError('Controller returned an incomplete application-state list')
64
+ found = [str(states[index]) for index in range(len(names)) if str(names[index]) == name]
65
+ if len(found) != 1 or found[0] == 'unknown':
66
+ raise RuntimeError('Controller application state is unavailable: ' + name)
67
+ return found[0]
68
+
69
+
70
+ def require_current_boot_project(core):
71
+ arguments = Array[Object]([False, False])
72
+ api = loaded_type('_3S.CoDeSys.Core.Online.IOnlineApplication8')
73
+ api.GetMethod('CheckFileConsistency').Invoke(core, arguments)
74
+ if not arguments[0]:
75
+ raise RuntimeError('Controller boot project does not match the downloaded application')
76
+
77
+
78
+ def create_controller_boot_project(core):
79
+ # Reflection retains the CLR stack IronPython otherwise maps to ValueError.
80
+ api = loaded_type('_3S.CoDeSys.Core.Online.IOnlineApplication3')
81
+ try:
82
+ api.GetMethod('CreateBootProject').Invoke(core, None)
83
+ except TargetInvocationException as error:
84
+ cause = error.InnerException if error.InnerException is not None else error
85
+ raise RuntimeError('Controller boot project creation failed:\n' + cause.ToString())
86
+
87
+
88
+ def generate_online_code(application):
89
+ environment = loaded_type('_3S.CoDeSys.ScriptDriverOnline.APEnvironment')
90
+ prop = environment.GetProperty('LanguageModelMgr', BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
91
+ manager = prop.GetValue(None, None)
92
+ if not manager.GenerateCode(application.guid, True, False):
93
+ raise RuntimeError('CODESYS cannot generate an online change for this source')
94
+
95
+
96
+ def consolidated_manager():
97
+ kind = loaded_type('_3S.CoDeSys.LanguageModelManager.LanguageModelManagerConsolidated')
98
+ return kind.GetProperty('_LMM', BindingFlags.Static | BindingFlags.NonPublic).GetValue(None, None)
99
+
100
+
101
+ def context_ids(context):
102
+ if context is None:
103
+ raise RuntimeError('Compiler context is unavailable')
104
+ ids = {'codeId': str(context.CodeId), 'dataId': str(context.DataId)}
105
+ if any(value == '00000000-0000-0000-0000-000000000000' for value in ids.values()):
106
+ raise RuntimeError('Compiler context has no generated code/data identity')
107
+ return ids
108
+
109
+
110
+ def load_downloaded_reference(application):
111
+ # Background reference loading may not have populated the language model
112
+ # when a new headless worker receives its first operation. This original
113
+ # synchronous path reads the candidate's saved compileinfo without building.
114
+ context = consolidated_manager().GetReferenceContextSynchronLoad(application.guid)
115
+ if context is None:
116
+ raise RuntimeError('Downloaded compiler reference could not be loaded')
117
+ return context_ids(context)
118
+
119
+
120
+ def remove_deleted(parent, previous, current):
121
+ """Reconcile only source-owned objects; preserve GUIDs of unchanged objects."""
122
+ new = {item['name'].lower(): item for item in current}
123
+ for old in previous:
124
+ obj = unique_child(parent, old['name'])
125
+ fresh = new.get(old['name'].lower())
126
+ if obj is None:
127
+ raise RuntimeError('Saved source object is missing: ' + old['name'])
128
+ if fresh is None or old['kind'] != fresh['kind'] or bool(old.get('persistent')) != bool(fresh.get('persistent')):
129
+ obj.remove()
130
+ else:
131
+ remove_deleted(obj, old.get('children', []), fresh.get('children', []))
132
+
133
+
134
+ def reconcile(application, previous, current):
135
+ # A folder move changes placement rather than text. Remove/recreate just
136
+ # that moved object, letting the vendor decide whether the update is valid.
137
+ new = {(tuple(item['folder']), item['name'].lower()): item for item in current}
138
+ for old in previous:
139
+ parent = application
140
+ for name in old['folder']:
141
+ parent = unique_child(parent, name)
142
+ if parent is None:
143
+ raise RuntimeError('Saved source folder is missing: ' + name)
144
+ fresh = new.get((tuple(old['folder']), old['name'].lower()))
145
+ remove_deleted(parent, [old], [fresh] if fresh else [])
146
+
147
+
148
+ def run_online(engine, request):
149
+ progress_path = request.get('progress')
150
+ def progress(stage):
151
+ if progress_path:
152
+ with io.open(progress_path, 'a', encoding='utf-8') as output:
153
+ output.write(u'{0}Z {1}\n'.format(time.strftime('%Y-%m-%dT%H:%M:%S', time.gmtime()), stage))
154
+ progress('Opening project; processor count {0}'.format(Environment.ProcessorCount))
155
+ project = engine.projects.open(request['project'], primary=True)
156
+ # Online changes reuse the downloaded project, which already has this controller.
157
+ if request['operation'] == 'download' and request.get('controller'):
158
+ apply_controller(project, request['controller'], progress)
159
+ application = project.active_application
160
+ online_app = None
161
+ online_device = None
162
+ try:
163
+ progress('Project opened; configuring controller connection')
164
+ if application is None:
165
+ raise RuntimeError('Project has no active Application')
166
+ device = application.parent
167
+ while device is not None and not getattr(device, 'is_device', False):
168
+ device = device.parent
169
+ if device is None:
170
+ raise RuntimeError('Application has no controller device')
171
+ settings = request['online']
172
+ gateway_ip = str(Dns.GetHostAddresses(settings['gateway']['host'])[0])
173
+ device_ip = str(Dns.GetHostAddresses(settings['device']['host'])[0])
174
+ gateway = engine.online.gateways.add_new_gateway('codesys-build', {0: gateway_ip, 1: settings['gateway']['port']})
175
+ device.set_gateway_and_ip_address(gateway.guid, IPAddress.Parse(device_ip), settings['device']['port'])
176
+ credentials = settings.get('credentials')
177
+ if credentials:
178
+ username, password = (os.environ.get(credentials[key]) for key in ('usernameEnv', 'passwordEnv'))
179
+ if not username or not password:
180
+ raise RuntimeError('CODESYS online credential environment variables are required')
181
+ engine.online.set_specific_credentials(device, username, password)
182
+ online_device = engine.online.create_online_device(device)
183
+ if request.get('initializeUser'):
184
+ if request['operation'] != 'download' or not credentials:
185
+ raise RuntimeError('Initial controller user requires an explicit download and credentials')
186
+ online_device.set_credentials_for_initial_user(username, password)
187
+ progress('Controller connection configured')
188
+ previous = request.get('baseline')
189
+ if request['operation'] != 'download':
190
+ online_app = engine.online.create_online_application(application)
191
+ core = core_application(online_app)
192
+ if not previous or str(application.guid) != previous['applicationGuid']:
193
+ raise RuntimeError('Online change requires the saved downloaded application')
194
+ progress('Checking downloaded controller identity and state')
195
+ session_login(core)
196
+ try:
197
+ observed = controller_ids(core)
198
+ if observed != previous['controllerIds']:
199
+ raise RuntimeError('Controller application differs from the saved baseline; use an explicit download')
200
+ progress('Loading downloaded compiler reference')
201
+ if load_downloaded_reference(application) != observed:
202
+ raise RuntimeError('Downloaded compiler reference differs from the controller; use an explicit download')
203
+ progress('Downloaded compiler reference loaded')
204
+ state = controller_state(core)
205
+ if request['operation'] == 'online-change' and state != 'run':
206
+ raise RuntimeError('Online change requires a running application')
207
+ if request['operation'] == 'inspect':
208
+ return {'applicationGuid': str(application.guid), 'controllerIds': observed,
209
+ 'state': state, 'values': {expression: online_app.read_value(expression) for expression in request.get('read', [])}}
210
+ finally:
211
+ core.Logout()
212
+ if request['operation'] == 'online-change':
213
+ reconcile(application, previous['objects'], request['objects'])
214
+ import_objects(engine, application, request, request['operation'] != 'download', progress)
215
+ if request['symbols'] and not any(getattr(child, 'is_symbol_config', False) for child in application.get_children()):
216
+ from System import Guid
217
+ application.create_symbol_config(True, True, Guid('{0141eb75-141b-4ea1-9a8c-75f952b22a6c}'))
218
+ # Match the normal build's source/template lifecycle. Saving the edited
219
+ # tree and clearing editor-load diagnostics happens before compilation.
220
+ project.save()
221
+ for category in engine.system.get_message_categories():
222
+ engine.system.clear_messages(category)
223
+ if request['operation'] == 'download':
224
+ # A full download replaces the previous compiler model. Use the
225
+ # normal build's clean/rebuild path before opening the online session.
226
+ progress('Rebuilding application for full download')
227
+ application.clean()
228
+ application.rebuild()
229
+ fail_on_errors(engine)
230
+ application.generate_code()
231
+ project.save()
232
+ else:
233
+ # Cleaning would discard the downloaded reference needed to decide
234
+ # whether a source edit can be applied as an online change.
235
+ progress('Building application incrementally for online change')
236
+ application.build()
237
+ fail_on_errors(engine)
238
+ if request['operation'] == 'download':
239
+ online_app = engine.online.create_online_application(application)
240
+ core = core_application(online_app)
241
+ progress('Downloading application')
242
+ online_app.login(engine.OnlineChangeOption.Never, False)
243
+ expected = controller_ids(core)
244
+ if controller_state(core) != 'run':
245
+ online_app.start()
246
+ else:
247
+ # Stay on the session-only/core route. High-level scripting Force
248
+ # still auto-creates absent applications; this route never creates
249
+ # an application and passes True explicitly to the download service.
250
+ progress('Rechecking controller identity before online change')
251
+ if not core.Login(True):
252
+ raise RuntimeError('Controller application disappeared before the online change')
253
+ if controller_ids(core) != previous['controllerIds']:
254
+ raise RuntimeError('Controller application changed before the online change')
255
+ expected = controller_ids(core)
256
+ if not application.is_uptodate:
257
+ if not application.is_online_change_possible:
258
+ raise RuntimeError('CODESYS requires a full download for this source; online change was refused')
259
+ progress('Generating online-change code')
260
+ generate_online_code(application)
261
+ fail_on_errors(engine)
262
+ expected = context_ids(consolidated_manager().GetCompileContext(application.guid))
263
+ mode = loaded_type('_3S.CoDeSys.Core.Online.BootProjectTransferMode')
264
+ progress('Applying online change')
265
+ core.Download(True, Enum.Parse(mode, 'UseProjectSetting'))
266
+ # An online-change transfer does not itself replace the boot project.
267
+ # The original online boot command saves the downloaded application;
268
+ # Boot-only host preparation saves the accepted application separately
269
+ # from the live transfer; it does not issue a controller full download.
270
+ progress('Saving running application as controller boot project')
271
+ create_controller_boot_project(core)
272
+ require_current_boot_project(core)
273
+ progress('Confirming activated code/data identity with a fresh controller login')
274
+ core.Logout()
275
+ session_login(core)
276
+ if controller_ids(core) != expected:
277
+ raise RuntimeError('Controller did not retain the downloaded code/data identity')
278
+ fail_on_errors(engine)
279
+ ids = controller_ids(core)
280
+ state = controller_state(core)
281
+ if state != 'run':
282
+ raise RuntimeError('Controller is not running after the operation')
283
+ progress('Reading controller values')
284
+ result = {'applicationGuid': str(application.guid), 'controllerIds': ids,
285
+ 'state': state, 'application': application.get_name(),
286
+ 'values': {expression: online_app.read_value(expression) for expression in request.get('read', [])}}
287
+ online_app.logout()
288
+ progress('Saving downloaded project')
289
+ project.save()
290
+ return result
291
+ finally:
292
+ if online_app is not None:
293
+ online_app.Dispose()
294
+ if online_device is not None:
295
+ online_device.Dispose()
296
+ # The original language-model manager joins its asynchronous downloaded
297
+ # reference saver on project close. Publication happens after this returns.
298
+ progress('Closing project and waiting for downloaded reference')
299
+ project.close()
300
+ progress('Project closed')