nodalis-compiler 1.0.21 → 1.0.23
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.
- package/CHANGELOG.md +3 -1
- package/README.md +34 -10
- package/package.json +1 -1
- package/src/compilers/ArduinoCompiler.js +18 -14
- package/src/compilers/CPPCompiler.js +32 -40
- package/src/compilers/Compiler.js +21 -0
- package/src/compilers/JSCompiler.js +2 -3
- package/src/compilers/arduinoDefaults.js +5 -0
- package/src/compilers/support/arduino/nodalis.h +3 -10
- package/src/nodalis.js +22 -1
- package/src/programmers/ArduinoProgrammer.js +6 -1
- package/src/toolchains.js +417 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [1.0.
|
|
3
|
+
## [1.0.23] 2026-03-03
|
|
4
4
|
- Changed IEC parser to interpret Function Blocks that are actually standard functions to a formal function call.
|
|
5
5
|
- Fixed nodejs/jint compiles to put executables in a bin folder.
|
|
6
6
|
- Fixed syntax errors with repeat.
|
|
7
|
+
- Prevent stale files in ST bundle compile.
|
|
8
|
+
- Added a new "action" for "get-toolchains", which will download the necessary toolchains for CPP and Arduino.
|
|
7
9
|
|
|
8
10
|
## [1.0.17] 2026-02-25
|
|
9
11
|
- Added support for compilation of multiple ST files as a single project.
|
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@ Usage:
|
|
|
56
56
|
Actions:
|
|
57
57
|
--action list-compilers
|
|
58
58
|
--action compile
|
|
59
|
+
--action get-toolchains
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
---
|
|
@@ -68,6 +69,18 @@ Actions:
|
|
|
68
69
|
nodalis --action list-compilers
|
|
69
70
|
```
|
|
70
71
|
|
|
72
|
+
### ✔ Install managed C/C++ toolchains
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
nodalis --action get-toolchains
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This installs:
|
|
79
|
+
- Managed Zig-based C/C++ cross-compilers
|
|
80
|
+
- Managed `arduino-cli`
|
|
81
|
+
- Default Arduino board cores for the built-in Nodalis FQBN targets
|
|
82
|
+
- Required Arduino libraries for the built-in Nodalis Arduino runtime, including `ArduinoModbus`
|
|
83
|
+
|
|
71
84
|
---
|
|
72
85
|
|
|
73
86
|
### ✔ Compile a Structured Text program
|
|
@@ -113,23 +126,34 @@ await app.compile({
|
|
|
113
126
|
|
|
114
127
|
#### Dependencies
|
|
115
128
|
|
|
116
|
-
- Uses
|
|
129
|
+
- Uses managed Zig-based toolchain wrappers under `~/.nodalis/toolchains` when no overrides are provided.
|
|
130
|
+
- Install those toolchains with `nodalis --action get-toolchains`.
|
|
131
|
+
- `get-toolchains` also installs a managed `arduino-cli` plus the default Nodalis Arduino board cores.
|
|
132
|
+
- `get-toolchains` also installs the default Arduino libraries required by the built-in runtime support.
|
|
133
|
+
- On Windows hosts, the managed toolchain installs wrappers for all Linux and Windows targets.
|
|
134
|
+
- On macOS hosts, the managed toolchain installs wrappers for all Linux, Windows, and macOS targets.
|
|
117
135
|
- Supply a `toolchain.json` file beside your source to describe a custom toolchain. Example:
|
|
118
136
|
|
|
119
137
|
```json
|
|
120
138
|
{
|
|
121
|
-
"linux-arm": "
|
|
122
|
-
"linux-arm64": "
|
|
123
|
-
"linux-x64": "
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"
|
|
139
|
+
"linux-arm": "/Users/you/.nodalis/toolchains/zig/0.15.2/wrappers/zig-linux-arm-c++",
|
|
140
|
+
"linux-arm64": "/Users/you/.nodalis/toolchains/zig/0.15.2/wrappers/zig-linux-arm64-c++",
|
|
141
|
+
"linux-x64": "/Users/you/.nodalis/toolchains/zig/0.15.2/wrappers/zig-linux-x64-c++",
|
|
142
|
+
"windows-x64": "/Users/you/.nodalis/toolchains/zig/0.15.2/wrappers/zig-windows-x64-c++",
|
|
143
|
+
"windows-arm64": "/Users/you/.nodalis/toolchains/zig/0.15.2/wrappers/zig-windows-arm64-c++",
|
|
144
|
+
"macos-x64": "/Users/you/.nodalis/toolchains/zig/0.15.2/wrappers/zig-macos-x64-c++",
|
|
145
|
+
"macos-arm64": "/Users/you/.nodalis/toolchains/zig/0.15.2/wrappers/zig-macos-arm64-c++"
|
|
128
146
|
}
|
|
129
147
|
```
|
|
130
148
|
|
|
131
|
-
-
|
|
132
|
-
-
|
|
149
|
+
- The managed installer skips macOS targets on non-macOS hosts.
|
|
150
|
+
- `toolchain.json` remains the escape hatch for unsupported or custom compiler setups.
|
|
151
|
+
|
|
152
|
+
### Arduino
|
|
153
|
+
|
|
154
|
+
Nodalis uses a managed `arduino-cli` by default when present under `~/.nodalis/toolchains/arduino-cli`.
|
|
155
|
+
Running `nodalis --action get-toolchains` also installs the default board cores required by the built-in Arduino FQBN targets, currently including `arduino:mbed_opta:opta`.
|
|
156
|
+
It also installs the default Arduino libraries required by the shipped support code, currently including `ArduinoModbus`.
|
|
133
157
|
|
|
134
158
|
#### Variations
|
|
135
159
|
|
package/package.json
CHANGED
|
@@ -23,14 +23,12 @@ import { Compiler, IECLanguage, OutputType, CommunicationProtocol } from './Comp
|
|
|
23
23
|
import * as iec from './iec-parser/parser.js';
|
|
24
24
|
import { parseStructuredText } from './st-parser/parser.js';
|
|
25
25
|
import { transpile } from './st-parser/gcctranspiler.js';
|
|
26
|
+
import { DEFAULT_ARDUINO_FQBN } from './arduinoDefaults.js';
|
|
27
|
+
import { getManagedArduinoCliPath, getManagedArduinoCliExecOptions } from '../toolchains.js';
|
|
26
28
|
|
|
27
29
|
const __filename = fileURLToPath(import.meta.url);
|
|
28
30
|
const __dirname = dirname(__filename);
|
|
29
31
|
|
|
30
|
-
const DEFAULT_ARDUINO_FQBN = {
|
|
31
|
-
'arduino-opta': 'arduino:mbed_opta:opta'
|
|
32
|
-
};
|
|
33
|
-
|
|
34
32
|
const MODBUS_ARDUINO_CORE_IDS = new Set([
|
|
35
33
|
'arduino:megaavr',
|
|
36
34
|
'arduino:samd',
|
|
@@ -115,6 +113,7 @@ export class ArduinoCompiler extends Compiler {
|
|
|
115
113
|
let sourceCode = '';
|
|
116
114
|
let bundleEntryProgram = '';
|
|
117
115
|
if (directoryBundleMode) {
|
|
116
|
+
this.cleanupStructuredTextBundleArtifacts(sourcePath);
|
|
118
117
|
const { combinedSource, entryProgramName } = this.loadStructuredTextBundle(sourcePath, resourceName);
|
|
119
118
|
sourceCode = combinedSource;
|
|
120
119
|
bundleEntryProgram = entryProgramName;
|
|
@@ -256,7 +255,7 @@ void loop() {
|
|
|
256
255
|
fs.cpSync(path.join(supportDir, 'json.hpp'), path.join(outputPath, 'json.hpp'), { force: true });
|
|
257
256
|
|
|
258
257
|
if (this.isExecutableOutput()) {
|
|
259
|
-
const arduinoCli = compilerConfig.arduino_cli || compilerConfig.arduinoCli ||
|
|
258
|
+
const arduinoCli = compilerConfig.arduino_cli || compilerConfig.arduinoCli || getManagedArduinoCliPath();
|
|
260
259
|
const arduinoFqbn = this.resolveArduinoFqbn(target, compilerConfig);
|
|
261
260
|
if (!arduinoFqbn) {
|
|
262
261
|
throw new Error(`No Arduino FQBN configured for target "${target}". Use an explicit FQBN target (e.g. arduino:mbed_opta:opta) or add "arduino_fqbn" to toolchain.json.`);
|
|
@@ -271,23 +270,25 @@ void loop() {
|
|
|
271
270
|
fs.mkdirSync(binDir, { recursive: true });
|
|
272
271
|
const arduinoCompileCmd = `${arduinoCli} compile --fqbn "${arduinoFqbn}" "${outputPath}" --build-path "${buildDir}" --output-dir "${binDir}" --export-binaries`;
|
|
273
272
|
try {
|
|
274
|
-
execSync(arduinoCompileCmd, { stdio: 'pipe' });
|
|
273
|
+
execSync(arduinoCompileCmd, getManagedArduinoCliExecOptions({ stdio: 'pipe' }));
|
|
275
274
|
} catch (err) {
|
|
276
275
|
const stderrText = getExecOutputText(err?.stderr);
|
|
277
276
|
const stdoutText = getExecOutputText(err?.stdout);
|
|
278
277
|
const compilerOutput = [stderrText, stdoutText].filter(Boolean).join('\n');
|
|
278
|
+
const missingArduinoLibrary = compilerOutput.includes('ArduinoModbus.h') || compilerOutput.includes('No such file or directory');
|
|
279
279
|
const details = compilerOutput || err.message;
|
|
280
280
|
throw new Error(
|
|
281
|
-
`Arduino CLI build failed for "${arduinoFqbn}".
|
|
281
|
+
`Arduino CLI build failed for "${arduinoFqbn}". ` +
|
|
282
|
+
`${missingArduinoLibrary
|
|
283
|
+
? 'Run "nodalis --action get-toolchains" to install the managed Arduino cores and libraries.'
|
|
284
|
+
: 'Verify arduino-cli, board core, and library availability.'}\n${details}`
|
|
282
285
|
);
|
|
283
286
|
}
|
|
284
287
|
}
|
|
285
288
|
}
|
|
286
289
|
|
|
287
290
|
loadStructuredTextBundle(sourcePath, resourceName) {
|
|
288
|
-
const stFiles =
|
|
289
|
-
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.st'))
|
|
290
|
-
.map((entry) => entry.name);
|
|
291
|
+
const stFiles = this.listStructuredTextBundleFiles(sourcePath);
|
|
291
292
|
|
|
292
293
|
if (stFiles.length === 0) {
|
|
293
294
|
throw new Error(`No .st files found in source directory "${sourcePath}".`);
|
|
@@ -354,7 +355,7 @@ void loop() {
|
|
|
354
355
|
|
|
355
356
|
let coreList = '';
|
|
356
357
|
try {
|
|
357
|
-
coreList = execSync(`${arduinoCli} core list`, { encoding: 'utf8' });
|
|
358
|
+
coreList = execSync(`${arduinoCli} core list`, getManagedArduinoCliExecOptions({ encoding: 'utf8' }));
|
|
358
359
|
} catch (err) {
|
|
359
360
|
throw new Error(`Failed to query installed Arduino cores using "${arduinoCli}". ${err.message}`);
|
|
360
361
|
}
|
|
@@ -363,8 +364,8 @@ void loop() {
|
|
|
363
364
|
}
|
|
364
365
|
|
|
365
366
|
try {
|
|
366
|
-
execSync(`${arduinoCli} core update-index`, { stdio: 'inherit' });
|
|
367
|
-
execSync(`${arduinoCli} core install ${coreId}`, { stdio: 'inherit' });
|
|
367
|
+
execSync(`${arduinoCli} core update-index`, getManagedArduinoCliExecOptions({ stdio: 'inherit' }));
|
|
368
|
+
execSync(`${arduinoCli} core install ${coreId}`, getManagedArduinoCliExecOptions({ stdio: 'inherit' }));
|
|
368
369
|
} catch (err) {
|
|
369
370
|
throw new Error(`Failed to install Arduino core "${coreId}" required for ${arduinoFqbn}. ${err.message}`);
|
|
370
371
|
}
|
|
@@ -388,7 +389,10 @@ void loop() {
|
|
|
388
389
|
const targets = Object.keys(DEFAULT_ARDUINO_FQBN);
|
|
389
390
|
|
|
390
391
|
try {
|
|
391
|
-
const boardListRaw = execSync(
|
|
392
|
+
const boardListRaw = execSync(
|
|
393
|
+
`${getManagedArduinoCliPath()} board listall --format json`,
|
|
394
|
+
getManagedArduinoCliExecOptions({ encoding: 'utf8' })
|
|
395
|
+
);
|
|
392
396
|
const boardList = JSON.parse(boardListRaw);
|
|
393
397
|
const boards = Array.isArray(boardList.boards) ? boardList.boards : [];
|
|
394
398
|
|
|
@@ -24,19 +24,12 @@ import { parseStructuredText } from './st-parser/parser.js';
|
|
|
24
24
|
import { transpile } from './st-parser/gcctranspiler.js';
|
|
25
25
|
import { fileURLToPath } from 'node:url';
|
|
26
26
|
import { dirname } from 'node:path';
|
|
27
|
+
import { getDefaultToolchain, getToolchainRoot, isManagedZigCompiler } from '../toolchains.js';
|
|
27
28
|
|
|
28
29
|
const __filename = fileURLToPath(import.meta.url);
|
|
29
30
|
const __dirname = dirname(__filename);
|
|
30
31
|
|
|
31
|
-
const DEFAULT_TOOLCHAIN =
|
|
32
|
-
"linux-arm": "arm-linux-gnueabi-g++",
|
|
33
|
-
"linux-arm64": "aarch64-linux-gnu-g++",
|
|
34
|
-
"linux-x64": "x86_64-linux-gnu-g++",
|
|
35
|
-
"macos-arm64": "clang++",
|
|
36
|
-
"macos-x64": "clang++",
|
|
37
|
-
"windows-x64": "x86_64-w64-mingw32-g++",
|
|
38
|
-
"windows-arm64": "/opt/llvm-mingw/bin/aarch64-w64-mingw32-g++"
|
|
39
|
-
};
|
|
32
|
+
const DEFAULT_TOOLCHAIN = getDefaultToolchain();
|
|
40
33
|
|
|
41
34
|
let ToolChain = { ...DEFAULT_TOOLCHAIN };
|
|
42
35
|
|
|
@@ -79,7 +72,7 @@ export class CPPCompiler extends Compiler {
|
|
|
79
72
|
const isStructuredTextLanguage = String(language || '').toUpperCase() === IECLanguage.STRUCTURED_TEXT;
|
|
80
73
|
const directoryBundleMode = sourceIsDirectory && isStructuredTextLanguage && typeof resourceName === 'string' && resourceName.trim().length > 0;
|
|
81
74
|
|
|
82
|
-
ToolChain = { ...
|
|
75
|
+
ToolChain = { ...getDefaultToolchain() };
|
|
83
76
|
const sourceDir = sourceIsDirectory ? sourcePath : path.dirname(sourcePath);
|
|
84
77
|
const toolchainConfigPath = path.join(sourceDir, "toolchain.json");
|
|
85
78
|
if (fs.existsSync(toolchainConfigPath)) {
|
|
@@ -103,6 +96,7 @@ export class CPPCompiler extends Compiler {
|
|
|
103
96
|
let bundleEntryProgram = '';
|
|
104
97
|
|
|
105
98
|
if (directoryBundleMode) {
|
|
99
|
+
this.cleanupStructuredTextBundleArtifacts(sourcePath);
|
|
106
100
|
const { combinedSource, entryProgramName } = this.loadStructuredTextBundle(sourcePath, resourceName);
|
|
107
101
|
sourceCode = combinedSource;
|
|
108
102
|
bundleEntryProgram = entryProgramName;
|
|
@@ -368,7 +362,12 @@ int main() {
|
|
|
368
362
|
.forEach((dir) => searchDirs.add(dir));
|
|
369
363
|
}
|
|
370
364
|
|
|
371
|
-
const compilerPath =
|
|
365
|
+
const compilerPath = path.isAbsolute(compiler)
|
|
366
|
+
? compiler
|
|
367
|
+
: execSync(process.platform === 'win32' ? `where "${compiler}"` : `which "${compiler}"`, { encoding: 'utf8' })
|
|
368
|
+
.split(/\r?\n/)
|
|
369
|
+
.map((line) => line.trim())
|
|
370
|
+
.find(Boolean);
|
|
372
371
|
if (compilerPath) {
|
|
373
372
|
const compilerDir = path.dirname(compilerPath);
|
|
374
373
|
searchDirs.add(compilerDir);
|
|
@@ -400,9 +399,7 @@ int main() {
|
|
|
400
399
|
}
|
|
401
400
|
|
|
402
401
|
loadStructuredTextBundle(sourcePath, resourceName) {
|
|
403
|
-
const stFiles =
|
|
404
|
-
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.st'))
|
|
405
|
-
.map((entry) => entry.name);
|
|
402
|
+
const stFiles = this.listStructuredTextBundleFiles(sourcePath);
|
|
406
403
|
|
|
407
404
|
if (stFiles.length === 0) {
|
|
408
405
|
throw new Error(`No .st files found in source directory "${sourcePath}".`);
|
|
@@ -501,44 +498,28 @@ int main() {
|
|
|
501
498
|
}
|
|
502
499
|
|
|
503
500
|
detectCompiler(hostOs, hostArch, targetOs, targetArch) {
|
|
504
|
-
const hostDefaults = {
|
|
505
|
-
linux: "g++",
|
|
506
|
-
macos: "clang++",
|
|
507
|
-
windows: "cl.exe"
|
|
508
|
-
};
|
|
509
|
-
const hostKey = `${hostOs}-${hostArch}`;
|
|
510
501
|
const targetKey = `${targetOs}-${targetArch}`;
|
|
511
502
|
|
|
512
503
|
const ensureCompilerAvailable = (compilerName, message) => {
|
|
513
|
-
const versionCommand = compilerName === "cl.exe" ? compilerName : `${compilerName} --version`;
|
|
514
504
|
try {
|
|
515
|
-
execSync(
|
|
505
|
+
execSync(`"${compilerName}" --version`, { stdio: 'ignore' });
|
|
516
506
|
} catch {
|
|
517
507
|
throw new Error(message);
|
|
518
508
|
}
|
|
519
509
|
};
|
|
520
510
|
|
|
521
|
-
if (targetKey === hostKey) {
|
|
522
|
-
const defaultCompiler = hostDefaults[hostOs];
|
|
523
|
-
if (!defaultCompiler) {
|
|
524
|
-
throw new Error(`No default compiler configured for host platform ${hostOs}.`);
|
|
525
|
-
}
|
|
526
|
-
ensureCompilerAvailable(
|
|
527
|
-
defaultCompiler,
|
|
528
|
-
`The default compiler "${defaultCompiler}" is not available. Install it using your package manager (e.g., brew install ${defaultCompiler} or apt install ${defaultCompiler}).
|
|
529
|
-
You can also create a file called "toolchain.json" in your source directory which will supply the path to the gnu c compiler for each platform. See the README file for more details.`
|
|
530
|
-
);
|
|
531
|
-
return defaultCompiler;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
511
|
const configuredCompiler = ToolChain[targetKey];
|
|
535
512
|
if (!configuredCompiler) {
|
|
536
|
-
throw new Error(
|
|
513
|
+
throw new Error(
|
|
514
|
+
`No toolchain is configured for target ${targetKey} on host ${hostOs}-${hostArch}. ` +
|
|
515
|
+
`Run "nodalis --action get-toolchains" or add an override in toolchain.json.`
|
|
516
|
+
);
|
|
537
517
|
}
|
|
538
518
|
ensureCompilerAvailable(
|
|
539
519
|
configuredCompiler,
|
|
540
|
-
`
|
|
541
|
-
|
|
520
|
+
`Toolchain "${configuredCompiler}" for target ${targetKey} is not available. ` +
|
|
521
|
+
`Run "nodalis --action get-toolchains" to install managed toolchains under ${getToolchainRoot()}, ` +
|
|
522
|
+
`or add a custom compiler path in toolchain.json.`
|
|
542
523
|
);
|
|
543
524
|
return configuredCompiler;
|
|
544
525
|
}
|
|
@@ -562,7 +543,7 @@ int main() {
|
|
|
562
543
|
if (compiler === 'cl.exe') {
|
|
563
544
|
return { c: [], cpp: [] };
|
|
564
545
|
}
|
|
565
|
-
let flags = { //default flags are for
|
|
546
|
+
let flags = { // default flags are for managed/native clang wrappers
|
|
566
547
|
'linux-x64': [],
|
|
567
548
|
'linux-arm64': [],
|
|
568
549
|
'linux-arm': [],
|
|
@@ -582,7 +563,18 @@ int main() {
|
|
|
582
563
|
"macos-arm64": "",
|
|
583
564
|
}
|
|
584
565
|
|
|
585
|
-
if (
|
|
566
|
+
if (isManagedZigCompiler(compiler)) {
|
|
567
|
+
flags = {
|
|
568
|
+
'linux-x64': [],
|
|
569
|
+
'linux-arm64': [],
|
|
570
|
+
'linux-arm': [],
|
|
571
|
+
'macos-x64': [],
|
|
572
|
+
'macos-arm64': [],
|
|
573
|
+
'windows-x64': [],
|
|
574
|
+
'windows-arm64': []
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
else if (!compiler.includes("clang")) {
|
|
586
578
|
flags = {
|
|
587
579
|
'linux-x64': ["-D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE -pthread"],
|
|
588
580
|
'linux-arm64': ["-D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE -pthread"],
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// See the License for the specific language governing permissions and
|
|
15
15
|
// limitations under the License.
|
|
16
16
|
|
|
17
|
+
import fs from 'fs';
|
|
17
18
|
import path from 'path';
|
|
18
19
|
|
|
19
20
|
export const IECLanguage = Object.freeze({
|
|
@@ -70,6 +71,26 @@ export class Compiler {
|
|
|
70
71
|
return path.join(this.options.outputPath, 'bin');
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
getStructuredTextBundleArtifactName() {
|
|
75
|
+
return 'nodalisplc.st';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
cleanupStructuredTextBundleArtifacts(sourcePath) {
|
|
79
|
+
const artifactPath = path.join(sourcePath, this.getStructuredTextBundleArtifactName());
|
|
80
|
+
if (fs.existsSync(artifactPath) && fs.lstatSync(artifactPath).isFile()) {
|
|
81
|
+
fs.rmSync(artifactPath, { force: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
listStructuredTextBundleFiles(sourcePath) {
|
|
86
|
+
const bundleArtifactName = this.getStructuredTextBundleArtifactName().toLowerCase();
|
|
87
|
+
return fs.readdirSync(sourcePath, { withFileTypes: true })
|
|
88
|
+
.filter((entry) => entry.isFile()
|
|
89
|
+
&& entry.name.toLowerCase().endsWith('.st')
|
|
90
|
+
&& entry.name.toLowerCase() !== bundleArtifactName)
|
|
91
|
+
.map((entry) => entry.name);
|
|
92
|
+
}
|
|
93
|
+
|
|
73
94
|
/** @returns {string[]} */
|
|
74
95
|
get supportedLanguages() {
|
|
75
96
|
throw new Error('supportedLanguages must be implemented by subclass.');
|
|
@@ -65,6 +65,7 @@ export class JSCompiler extends Compiler {
|
|
|
65
65
|
let sourceCode = '';
|
|
66
66
|
let bundleEntryProgram = '';
|
|
67
67
|
if (directoryBundleMode) {
|
|
68
|
+
this.cleanupStructuredTextBundleArtifacts(sourcePath);
|
|
68
69
|
const { combinedSource, entryProgramName } = this.loadStructuredTextBundle(sourcePath, resourceName);
|
|
69
70
|
sourceCode = combinedSource;
|
|
70
71
|
bundleEntryProgram = entryProgramName;
|
|
@@ -314,9 +315,7 @@ export function run(){
|
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
JSCompiler.prototype.loadStructuredTextBundle = function (sourcePath, resourceName) {
|
|
317
|
-
const stFiles =
|
|
318
|
-
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.st'))
|
|
319
|
-
.map((entry) => entry.name);
|
|
318
|
+
const stFiles = this.listStructuredTextBundleFiles(sourcePath);
|
|
320
319
|
|
|
321
320
|
if (stFiles.length === 0) {
|
|
322
321
|
throw new Error(`No .st files found in source directory "${sourcePath}".`);
|
|
@@ -671,19 +671,12 @@ template <typename T, typename... Ts>
|
|
|
671
671
|
inline T MUX(std::size_t K, T in0, Ts... rest)
|
|
672
672
|
{
|
|
673
673
|
constexpr std::size_t N = 1 + sizeof...(Ts);
|
|
674
|
+
T values[N] = {in0, static_cast<T>(rest)...};
|
|
674
675
|
if (K >= N)
|
|
675
676
|
{
|
|
676
|
-
|
|
677
|
+
return values[0];
|
|
677
678
|
}
|
|
678
|
-
|
|
679
|
-
auto values = std::tuple<T, Ts...>(in0, rest...);
|
|
680
|
-
return std::apply(
|
|
681
|
-
[K](auto... elems) -> T
|
|
682
|
-
{
|
|
683
|
-
T arr[] = {elems...};
|
|
684
|
-
return arr[K];
|
|
685
|
-
},
|
|
686
|
-
values);
|
|
679
|
+
return values[K];
|
|
687
680
|
};
|
|
688
681
|
|
|
689
682
|
// ============================================================
|
package/src/nodalis.js
CHANGED
|
@@ -30,6 +30,7 @@ import { FileProgrammer } from './programmers/FileProgrammer.js';
|
|
|
30
30
|
import { SSHProgrammer } from './programmers/SSHProgrammer.js';
|
|
31
31
|
import { ArduinoProgrammer } from './programmers/ArduinoProgrammer.js';
|
|
32
32
|
import { CompileList } from "mticp-npm"
|
|
33
|
+
import { getToolchainRoot, installDefaultToolchains } from './toolchains.js';
|
|
33
34
|
|
|
34
35
|
const __filename = fileURLToPath(import.meta.url);
|
|
35
36
|
const __dirname = path.dirname(__filename);
|
|
@@ -172,6 +173,10 @@ export class Nodalis {
|
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
async getToolchains() {
|
|
177
|
+
return installDefaultToolchains();
|
|
178
|
+
}
|
|
179
|
+
|
|
175
180
|
}
|
|
176
181
|
|
|
177
182
|
function isCliEntryPoint() {
|
|
@@ -221,6 +226,11 @@ Actions:
|
|
|
221
226
|
--sshPort Optional SSH port for SSH deployment.
|
|
222
227
|
--arduinoFqbn Required for Arduino target if --target Arduino.
|
|
223
228
|
|
|
229
|
+
--action get-toolchains
|
|
230
|
+
Detects the host OS/arch and installs managed C/C++ toolchains,
|
|
231
|
+
arduino-cli, and default Arduino board cores under:
|
|
232
|
+
${getToolchainRoot()}
|
|
233
|
+
|
|
224
234
|
Examples:
|
|
225
235
|
node nodalis.js --action list-compilers
|
|
226
236
|
|
|
@@ -231,6 +241,8 @@ Examples:
|
|
|
231
241
|
--resourceName MyPLC \\
|
|
232
242
|
--sourcePath ./examples/pump.iec \\
|
|
233
243
|
--language st
|
|
244
|
+
|
|
245
|
+
node nodalis.js --action get-toolchains
|
|
234
246
|
`);
|
|
235
247
|
process.exit(0);
|
|
236
248
|
}
|
|
@@ -296,9 +308,18 @@ Examples:
|
|
|
296
308
|
break;
|
|
297
309
|
}
|
|
298
310
|
|
|
311
|
+
case 'get-toolchains': {
|
|
312
|
+
app.getToolchains().then((result) => {
|
|
313
|
+
console.log(JSON.stringify(result, null, 2));
|
|
314
|
+
}).catch(err => {
|
|
315
|
+
console.error(`Toolchain installation failed: ${err.message}`);
|
|
316
|
+
});
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
|
|
299
320
|
default: {
|
|
300
321
|
console.error(`Unknown or missing action: ${argMap.action}`);
|
|
301
|
-
console.error(`Valid actions: list-compilers, list-programmers, compile, deploy`);
|
|
322
|
+
console.error(`Valid actions: list-compilers, list-programmers, compile, deploy, get-toolchains`);
|
|
302
323
|
break;
|
|
303
324
|
}
|
|
304
325
|
}
|
|
@@ -16,6 +16,7 @@ import fs from 'fs/promises';
|
|
|
16
16
|
import path from 'path';
|
|
17
17
|
import { Programmer } from './Programmer.js';
|
|
18
18
|
import { runCommand } from './utils.js';
|
|
19
|
+
import { getManagedArduinoCliExecOptions, getManagedArduinoCliPath } from '../toolchains.js';
|
|
19
20
|
|
|
20
21
|
export class ArduinoProgrammer extends Programmer {
|
|
21
22
|
constructor(options) {
|
|
@@ -58,7 +59,11 @@ export class ArduinoProgrammer extends Programmer {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
const { stdout, stderr } = await runCommand(
|
|
62
|
+
const { stdout, stderr } = await runCommand(
|
|
63
|
+
getManagedArduinoCliPath(),
|
|
64
|
+
args,
|
|
65
|
+
getManagedArduinoCliExecOptions()
|
|
66
|
+
);
|
|
62
67
|
if (stdout) {
|
|
63
68
|
console.log(stdout.trim());
|
|
64
69
|
}
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import https from 'https';
|
|
5
|
+
import { execFileSync } from 'child_process';
|
|
6
|
+
import { pipeline } from 'stream/promises';
|
|
7
|
+
import { DEFAULT_ARDUINO_FQBNS } from './compilers/arduinoDefaults.js';
|
|
8
|
+
|
|
9
|
+
const ZIG_VERSION = '0.15.2';
|
|
10
|
+
const ARDUINO_CLI_VERSION = '1.3.1';
|
|
11
|
+
const TOOLCHAIN_ROOT = path.join(os.homedir(), '.nodalis', 'toolchains');
|
|
12
|
+
const WRAPPER_EXTENSION = process.platform === 'win32' ? '.cmd' : '';
|
|
13
|
+
const DEFAULT_ARDUINO_LIBRARIES = ['ArduinoModbus'];
|
|
14
|
+
|
|
15
|
+
const TARGET_TRIPLES = {
|
|
16
|
+
'linux-arm': 'arm-linux-gnueabihf',
|
|
17
|
+
'linux-arm64': 'aarch64-linux-gnu',
|
|
18
|
+
'linux-x64': 'x86_64-linux-gnu',
|
|
19
|
+
'macos-arm64': 'aarch64-macos',
|
|
20
|
+
'macos-x64': 'x86_64-macos',
|
|
21
|
+
'windows-arm64': 'aarch64-windows-gnu',
|
|
22
|
+
'windows-x64': 'x86_64-windows-gnu'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const normalizeHostOS = (value) => {
|
|
26
|
+
if (value === 'win32') return 'windows';
|
|
27
|
+
if (value === 'darwin') return 'macos';
|
|
28
|
+
if (value === 'linux') return 'linux';
|
|
29
|
+
throw new Error(`Unsupported host operating system: ${value}`);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const normalizeHostArch = (value) => {
|
|
33
|
+
if (value === 'x64') return 'x64';
|
|
34
|
+
if (value === 'arm64') return 'arm64';
|
|
35
|
+
if (value.startsWith('arm')) return 'arm';
|
|
36
|
+
throw new Error(`Unsupported host architecture: ${value}`);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const stripArchiveExtension = (archiveName) => {
|
|
40
|
+
if (archiveName.endsWith('.tar.gz')) return archiveName.slice(0, -7);
|
|
41
|
+
if (archiveName.endsWith('.tar.xz')) return archiveName.slice(0, -7);
|
|
42
|
+
if (archiveName.endsWith('.zip')) return archiveName.slice(0, -4);
|
|
43
|
+
return archiveName;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const getWrapperDir = () => path.join(TOOLCHAIN_ROOT, 'zig', ZIG_VERSION, 'wrappers');
|
|
47
|
+
|
|
48
|
+
const getWrapperPath = (target) => path.join(getWrapperDir(), `zig-${target}-c++${WRAPPER_EXTENSION}`);
|
|
49
|
+
|
|
50
|
+
const getArduinoCliRootDir = () => path.join(TOOLCHAIN_ROOT, 'arduino-cli', ARDUINO_CLI_VERSION);
|
|
51
|
+
|
|
52
|
+
const getArduinoCliDataDir = () => path.join(getArduinoCliRootDir(), 'data');
|
|
53
|
+
|
|
54
|
+
const getArduinoCliDownloadsDir = () => path.join(getArduinoCliRootDir(), 'downloads');
|
|
55
|
+
|
|
56
|
+
const getArduinoCliUserDir = () => path.join(getArduinoCliRootDir(), 'user');
|
|
57
|
+
|
|
58
|
+
const getZigInstallDir = (host = detectHostPlatform()) => {
|
|
59
|
+
const archiveName = getZigArchiveName(host);
|
|
60
|
+
return path.join(TOOLCHAIN_ROOT, 'zig', ZIG_VERSION, stripArchiveExtension(archiveName));
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const getZigBinaryPath = (host = detectHostPlatform()) => {
|
|
64
|
+
const installDir = getZigInstallDir(host);
|
|
65
|
+
return path.join(installDir, process.platform === 'win32' ? 'zig.exe' : 'zig');
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const getArduinoCliArchiveName = (host = detectHostPlatform()) => {
|
|
69
|
+
if (host.os === 'windows') {
|
|
70
|
+
const archSegment = host.arch === 'arm64' ? 'Windows_ARM64' : host.arch === 'x64' ? 'Windows_64bit' : null;
|
|
71
|
+
if (!archSegment) {
|
|
72
|
+
throw new Error(`Unsupported Windows host architecture for arduino-cli: ${host.arch}`);
|
|
73
|
+
}
|
|
74
|
+
return `arduino-cli_${ARDUINO_CLI_VERSION}_${archSegment}.zip`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (host.os === 'macos') {
|
|
78
|
+
const archSegment = host.arch === 'arm64' ? 'macOS_ARM64' : host.arch === 'x64' ? 'macOS_64bit' : null;
|
|
79
|
+
if (!archSegment) {
|
|
80
|
+
throw new Error(`Unsupported macOS host architecture for arduino-cli: ${host.arch}`);
|
|
81
|
+
}
|
|
82
|
+
return `arduino-cli_${ARDUINO_CLI_VERSION}_${archSegment}.tar.gz`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (host.os === 'linux') {
|
|
86
|
+
if (host.arch === 'x64') return `arduino-cli_${ARDUINO_CLI_VERSION}_Linux_64bit.tar.gz`;
|
|
87
|
+
if (host.arch === 'arm64') return `arduino-cli_${ARDUINO_CLI_VERSION}_Linux_ARM64.tar.gz`;
|
|
88
|
+
if (host.arch === 'arm') return `arduino-cli_${ARDUINO_CLI_VERSION}_Linux_ARMv7.tar.gz`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
throw new Error(`Unsupported arduino-cli host platform ${host.os}-${host.arch}`);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const getArduinoCliInstallDir = (host = detectHostPlatform()) => {
|
|
95
|
+
return path.join(getArduinoCliRootDir(), `${host.os}-${host.arch}`);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const getManagedArduinoCliPath = (host = detectHostPlatform()) => {
|
|
99
|
+
const installDir = getArduinoCliInstallDir(host);
|
|
100
|
+
return path.join(installDir, host.os === 'windows' ? 'arduino-cli.exe' : 'arduino-cli');
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const getSupportedTargetsForHost = (host = detectHostPlatform()) => {
|
|
104
|
+
const targets = ['linux-arm', 'linux-arm64', 'linux-x64', 'windows-arm64', 'windows-x64'];
|
|
105
|
+
if (host.os === 'macos') {
|
|
106
|
+
targets.push('macos-arm64', 'macos-x64');
|
|
107
|
+
}
|
|
108
|
+
return targets;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const getZigArchiveName = (host = detectHostPlatform()) => {
|
|
112
|
+
if (host.os === 'windows') {
|
|
113
|
+
const archSegment = host.arch === 'arm64' ? 'aarch64' : host.arch === 'x64' ? 'x86_64' : null;
|
|
114
|
+
if (!archSegment) {
|
|
115
|
+
throw new Error(`Unsupported Windows host architecture for Zig: ${host.arch}`);
|
|
116
|
+
}
|
|
117
|
+
return `zig-${archSegment}-windows-${ZIG_VERSION}.zip`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (host.os === 'macos') {
|
|
121
|
+
const archSegment = host.arch === 'arm64' ? 'aarch64' : host.arch === 'x64' ? 'x86_64' : null;
|
|
122
|
+
if (!archSegment) {
|
|
123
|
+
throw new Error(`Unsupported macOS host architecture for Zig: ${host.arch}`);
|
|
124
|
+
}
|
|
125
|
+
return `zig-${archSegment}-macos-${ZIG_VERSION}.tar.xz`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (host.os === 'linux') {
|
|
129
|
+
if (host.arch === 'x64') return `zig-x86_64-linux-${ZIG_VERSION}.tar.xz`;
|
|
130
|
+
if (host.arch === 'arm64') return `zig-aarch64-linux-${ZIG_VERSION}.tar.xz`;
|
|
131
|
+
if (host.arch === 'arm') return `zig-arm-linux-${ZIG_VERSION}.tar.xz`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
throw new Error(`Unsupported Zig host platform ${host.os}-${host.arch}`);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const writeWrapper = (wrapperPath, zigBinaryPath, targetTriple) => {
|
|
138
|
+
fs.mkdirSync(path.dirname(wrapperPath), { recursive: true });
|
|
139
|
+
|
|
140
|
+
if (process.platform === 'win32') {
|
|
141
|
+
const windowsScript = [
|
|
142
|
+
'@echo off',
|
|
143
|
+
`"%~dp0..\\${path.basename(path.dirname(zigBinaryPath))}\\zig.exe" c++ -target ${targetTriple} %*`
|
|
144
|
+
].join('\r\n');
|
|
145
|
+
fs.writeFileSync(wrapperPath, windowsScript);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const posixScript = [
|
|
150
|
+
'#!/bin/sh',
|
|
151
|
+
`exec "${zigBinaryPath}" c++ -target ${targetTriple} "$@"`
|
|
152
|
+
].join('\n');
|
|
153
|
+
fs.writeFileSync(wrapperPath, posixScript, { mode: 0o755 });
|
|
154
|
+
fs.chmodSync(wrapperPath, 0o755);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const ensureWrappers = (host = detectHostPlatform()) => {
|
|
158
|
+
const zigBinaryPath = getZigBinaryPath(host);
|
|
159
|
+
for (const target of getSupportedTargetsForHost(host)) {
|
|
160
|
+
const targetTriple = TARGET_TRIPLES[target];
|
|
161
|
+
if (!targetTriple) continue;
|
|
162
|
+
writeWrapper(getWrapperPath(target), zigBinaryPath, targetTriple);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const detectHostPlatform = () => ({
|
|
167
|
+
os: normalizeHostOS(os.platform()),
|
|
168
|
+
arch: normalizeHostArch(os.arch())
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
export const getToolchainRoot = () => TOOLCHAIN_ROOT;
|
|
172
|
+
|
|
173
|
+
export const getManagedArduinoCliExecOptions = (overrides = {}) => ({
|
|
174
|
+
...overrides,
|
|
175
|
+
env: {
|
|
176
|
+
...process.env,
|
|
177
|
+
ARDUINO_DIRECTORIES_DATA: getArduinoCliDataDir(),
|
|
178
|
+
ARDUINO_DIRECTORIES_DOWNLOADS: getArduinoCliDownloadsDir(),
|
|
179
|
+
ARDUINO_DIRECTORIES_USER: getArduinoCliUserDir(),
|
|
180
|
+
...(overrides.env || {})
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
export const isManagedZigCompiler = (compilerPath) => String(compilerPath || '').includes(`${path.sep}.nodalis${path.sep}toolchains${path.sep}zig${path.sep}`);
|
|
185
|
+
|
|
186
|
+
export const getDefaultToolchain = (host = detectHostPlatform()) => {
|
|
187
|
+
return getSupportedTargetsForHost(host).reduce((acc, target) => {
|
|
188
|
+
acc[target] = getWrapperPath(target);
|
|
189
|
+
return acc;
|
|
190
|
+
}, {});
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const downloadFile = async (url, destinationPath) => {
|
|
194
|
+
await new Promise((resolve, reject) => {
|
|
195
|
+
const request = https.get(url, (response) => {
|
|
196
|
+
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
197
|
+
response.resume();
|
|
198
|
+
downloadFile(response.headers.location, destinationPath).then(resolve).catch(reject);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (response.statusCode !== 200) {
|
|
203
|
+
response.resume();
|
|
204
|
+
reject(new Error(`Download failed with HTTP ${response.statusCode} for ${url}`));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const fileStream = fs.createWriteStream(destinationPath);
|
|
209
|
+
pipeline(response, fileStream).then(resolve).catch(reject);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
request.on('error', reject);
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const extractArchive = (archivePath, destinationDir) => {
|
|
217
|
+
fs.mkdirSync(destinationDir, { recursive: true });
|
|
218
|
+
|
|
219
|
+
if (archivePath.endsWith('.zip')) {
|
|
220
|
+
if (process.platform !== 'win32') {
|
|
221
|
+
throw new Error(`ZIP extraction is only configured for Windows hosts: ${archivePath}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
execFileSync(
|
|
225
|
+
'powershell.exe',
|
|
226
|
+
[
|
|
227
|
+
'-NoProfile',
|
|
228
|
+
'-NonInteractive',
|
|
229
|
+
'-Command',
|
|
230
|
+
`Expand-Archive -LiteralPath '${archivePath.replace(/'/g, "''")}' -DestinationPath '${destinationDir.replace(/'/g, "''")}' -Force`
|
|
231
|
+
],
|
|
232
|
+
{ stdio: 'inherit' }
|
|
233
|
+
);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
execFileSync('tar', ['-xf', archivePath, '-C', destinationDir], { stdio: 'inherit' });
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const ensureArduinoCliDirs = () => {
|
|
241
|
+
fs.mkdirSync(getArduinoCliRootDir(), { recursive: true });
|
|
242
|
+
fs.mkdirSync(getArduinoCliDataDir(), { recursive: true });
|
|
243
|
+
fs.mkdirSync(getArduinoCliDownloadsDir(), { recursive: true });
|
|
244
|
+
fs.mkdirSync(getArduinoCliUserDir(), { recursive: true });
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const installDefaultArduinoCores = (arduinoCliPath) => {
|
|
248
|
+
execFileSync(arduinoCliPath, ['core', 'update-index'], getManagedArduinoCliExecOptions({ stdio: 'inherit' }));
|
|
249
|
+
|
|
250
|
+
const coreIds = [...new Set(
|
|
251
|
+
DEFAULT_ARDUINO_FQBNS.map((fqbn) => {
|
|
252
|
+
const parts = String(fqbn).split(':');
|
|
253
|
+
return parts.length >= 2 ? `${parts[0]}:${parts[1]}` : null;
|
|
254
|
+
}).filter(Boolean)
|
|
255
|
+
)];
|
|
256
|
+
|
|
257
|
+
for (const coreId of coreIds) {
|
|
258
|
+
execFileSync(arduinoCliPath, ['core', 'install', coreId], getManagedArduinoCliExecOptions({ stdio: 'inherit' }));
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const installDefaultArduinoLibraries = (arduinoCliPath) => {
|
|
263
|
+
for (const libraryName of DEFAULT_ARDUINO_LIBRARIES) {
|
|
264
|
+
execFileSync(
|
|
265
|
+
arduinoCliPath,
|
|
266
|
+
['lib', 'install', libraryName],
|
|
267
|
+
getManagedArduinoCliExecOptions({ stdio: 'inherit' })
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export const installDefaultToolchains = async () => {
|
|
273
|
+
const host = detectHostPlatform();
|
|
274
|
+
const archiveName = getZigArchiveName(host);
|
|
275
|
+
const rootDir = path.join(TOOLCHAIN_ROOT, 'zig', ZIG_VERSION);
|
|
276
|
+
const archivePath = path.join(rootDir, archiveName);
|
|
277
|
+
const installDir = getZigInstallDir(host);
|
|
278
|
+
const zigBinaryPath = getZigBinaryPath(host);
|
|
279
|
+
|
|
280
|
+
fs.mkdirSync(rootDir, { recursive: true });
|
|
281
|
+
|
|
282
|
+
const results = [];
|
|
283
|
+
if (!fs.existsSync(zigBinaryPath)) {
|
|
284
|
+
const downloadUrl = `https://ziglang.org/download/${ZIG_VERSION}/${archiveName}`;
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
await downloadFile(downloadUrl, archivePath);
|
|
288
|
+
extractArchive(archivePath, rootDir);
|
|
289
|
+
fs.rmSync(archivePath, { force: true });
|
|
290
|
+
results.push({
|
|
291
|
+
id: 'zig',
|
|
292
|
+
status: 'installed',
|
|
293
|
+
installDir,
|
|
294
|
+
downloadUrl
|
|
295
|
+
});
|
|
296
|
+
} catch (err) {
|
|
297
|
+
fs.rmSync(archivePath, { force: true });
|
|
298
|
+
results.push({
|
|
299
|
+
id: 'zig',
|
|
300
|
+
status: 'failed',
|
|
301
|
+
downloadUrl,
|
|
302
|
+
error: err.message
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
host,
|
|
306
|
+
rootDir: TOOLCHAIN_ROOT,
|
|
307
|
+
toolchains: results,
|
|
308
|
+
defaultToolchain: getDefaultToolchain(host)
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
results.push({
|
|
313
|
+
id: 'zig',
|
|
314
|
+
status: 'already-installed',
|
|
315
|
+
installDir
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
ensureWrappers(host);
|
|
320
|
+
|
|
321
|
+
const arduinoArchiveName = getArduinoCliArchiveName(host);
|
|
322
|
+
const arduinoRootDir = getArduinoCliRootDir();
|
|
323
|
+
const arduinoArchivePath = path.join(arduinoRootDir, arduinoArchiveName);
|
|
324
|
+
const arduinoInstallDir = getArduinoCliInstallDir(host);
|
|
325
|
+
const arduinoCliPath = getManagedArduinoCliPath(host);
|
|
326
|
+
|
|
327
|
+
ensureArduinoCliDirs();
|
|
328
|
+
|
|
329
|
+
if (!fs.existsSync(arduinoCliPath)) {
|
|
330
|
+
const downloadUrl = `https://downloads.arduino.cc/arduino-cli/${arduinoArchiveName}`;
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
await downloadFile(downloadUrl, arduinoArchivePath);
|
|
334
|
+
fs.mkdirSync(arduinoInstallDir, { recursive: true });
|
|
335
|
+
extractArchive(arduinoArchivePath, arduinoInstallDir);
|
|
336
|
+
fs.rmSync(arduinoArchivePath, { force: true });
|
|
337
|
+
results.push({
|
|
338
|
+
id: 'arduino-cli',
|
|
339
|
+
status: 'installed',
|
|
340
|
+
installDir: arduinoInstallDir,
|
|
341
|
+
downloadUrl
|
|
342
|
+
});
|
|
343
|
+
} catch (err) {
|
|
344
|
+
fs.rmSync(arduinoArchivePath, { force: true });
|
|
345
|
+
results.push({
|
|
346
|
+
id: 'arduino-cli',
|
|
347
|
+
status: 'failed',
|
|
348
|
+
downloadUrl,
|
|
349
|
+
error: err.message
|
|
350
|
+
});
|
|
351
|
+
return {
|
|
352
|
+
host,
|
|
353
|
+
rootDir: TOOLCHAIN_ROOT,
|
|
354
|
+
toolchains: results,
|
|
355
|
+
defaultToolchain: getDefaultToolchain(host),
|
|
356
|
+
arduinoCli: arduinoCliPath,
|
|
357
|
+
defaultArduinoFqbns: DEFAULT_ARDUINO_FQBNS
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
results.push({
|
|
362
|
+
id: 'arduino-cli',
|
|
363
|
+
status: 'already-installed',
|
|
364
|
+
installDir: arduinoInstallDir
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
installDefaultArduinoCores(arduinoCliPath);
|
|
370
|
+
results.push({
|
|
371
|
+
id: 'arduino-default-cores',
|
|
372
|
+
status: 'installed',
|
|
373
|
+
fqbns: DEFAULT_ARDUINO_FQBNS
|
|
374
|
+
});
|
|
375
|
+
} catch (err) {
|
|
376
|
+
results.push({
|
|
377
|
+
id: 'arduino-default-cores',
|
|
378
|
+
status: 'failed',
|
|
379
|
+
fqbns: DEFAULT_ARDUINO_FQBNS,
|
|
380
|
+
error: err.message
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
installDefaultArduinoLibraries(arduinoCliPath);
|
|
386
|
+
results.push({
|
|
387
|
+
id: 'arduino-default-libraries',
|
|
388
|
+
status: 'installed',
|
|
389
|
+
libraries: DEFAULT_ARDUINO_LIBRARIES
|
|
390
|
+
});
|
|
391
|
+
} catch (err) {
|
|
392
|
+
results.push({
|
|
393
|
+
id: 'arduino-default-libraries',
|
|
394
|
+
status: 'failed',
|
|
395
|
+
libraries: DEFAULT_ARDUINO_LIBRARIES,
|
|
396
|
+
error: err.message
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
host,
|
|
402
|
+
rootDir: TOOLCHAIN_ROOT,
|
|
403
|
+
toolchains: [
|
|
404
|
+
...results,
|
|
405
|
+
...getSupportedTargetsForHost(host).map((target) => ({
|
|
406
|
+
id: target,
|
|
407
|
+
status: 'ready',
|
|
408
|
+
compiler: getWrapperPath(target),
|
|
409
|
+
targetTriple: TARGET_TRIPLES[target]
|
|
410
|
+
}))
|
|
411
|
+
],
|
|
412
|
+
defaultToolchain: getDefaultToolchain(host),
|
|
413
|
+
arduinoCli: arduinoCliPath,
|
|
414
|
+
defaultArduinoFqbns: DEFAULT_ARDUINO_FQBNS,
|
|
415
|
+
defaultArduinoLibraries: DEFAULT_ARDUINO_LIBRARIES
|
|
416
|
+
};
|
|
417
|
+
};
|