209 lines
6.0 KiB
JavaScript
209 lines
6.0 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const { execFileSync } = require('child_process');
|
|
|
|
const STATIC_INSTALL_DIRS = [
|
|
process.env.SEGGER_JLINK_PATH,
|
|
process.env.JLINK_PATH,
|
|
'C:\\Program Files\\SEGGER\\JLink',
|
|
'C:\\Program Files\\SEGGER\\JLink_V942',
|
|
'C:\\Program Files (x86)\\SEGGER\\JLink',
|
|
'C:\\Program Files (x86)\\SEGGER\\JLink_V942',
|
|
path.join(process.env.ProgramFiles || '', 'SEGGER', 'JLink'),
|
|
path.join(process.env.ProgramFiles || '', 'SEGGER', 'JLink_V942'),
|
|
path.join(process.env['ProgramFiles(x86)'] || '', 'SEGGER', 'JLink'),
|
|
path.join(process.env['ProgramFiles(x86)'] || '', 'SEGGER', 'JLink_V942'),
|
|
'C:\\nrfutil\\bin',
|
|
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'SEGGER', 'JLink'),
|
|
].filter(Boolean);
|
|
|
|
const SEGGER_ROOTS = [
|
|
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'SEGGER'),
|
|
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'SEGGER'),
|
|
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'SEGGER'),
|
|
];
|
|
|
|
/** SEGGER installer default: Program Files\SEGGER\JLink_Vxxx */
|
|
const discoverSeggerJLinkDirs = () => {
|
|
const discovered = [];
|
|
|
|
for (const root of SEGGER_ROOTS) {
|
|
if (!root || !fs.existsSync(root)) continue;
|
|
|
|
let entries = [];
|
|
try {
|
|
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
} catch {
|
|
continue;
|
|
}
|
|
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory()) continue;
|
|
if (!/^JLink/i.test(entry.name)) continue;
|
|
discovered.push(path.join(root, entry.name));
|
|
}
|
|
}
|
|
|
|
return discovered;
|
|
};
|
|
|
|
const parseJLinkFolderVersion = directory => {
|
|
const name = path.basename(directory);
|
|
const match = name.match(/^JLink(?:_V)?(.+)$/i);
|
|
if (!match) {
|
|
return { sortKey: 0, label: name };
|
|
}
|
|
|
|
const token = match[1];
|
|
const numeric = Number.parseInt(token, 10) || 0;
|
|
const suffix = token.slice(String(numeric).length);
|
|
|
|
return {
|
|
sortKey: numeric * 1000 + (suffix ? suffix.charCodeAt(0) : 0),
|
|
label: name,
|
|
};
|
|
};
|
|
|
|
/** Prefer newest SEGGER pack (e.g. JLink_V942 over JLink_V898). */
|
|
const sortInstallDirsNewestFirst = directories =>
|
|
[...directories].sort((left, right) => {
|
|
const leftVersion = parseJLinkFolderVersion(left);
|
|
const rightVersion = parseJLinkFolderVersion(right);
|
|
if (rightVersion.sortKey !== leftVersion.sortKey) {
|
|
return rightVersion.sortKey - leftVersion.sortKey;
|
|
}
|
|
|
|
try {
|
|
return fs.statSync(right).mtimeMs - fs.statSync(left).mtimeMs;
|
|
} catch {
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
const getInstallDirectories = () => {
|
|
const envDirs = [process.env.SEGGER_JLINK_PATH, process.env.JLINK_PATH].filter(Boolean);
|
|
const discovered = sortInstallDirsNewestFirst(discoverSeggerJLinkDirs());
|
|
const staticDirs = STATIC_INSTALL_DIRS.filter(
|
|
directory => directory && !envDirs.includes(directory)
|
|
);
|
|
|
|
return [...new Set([...envDirs, ...discovered, ...staticDirs])];
|
|
};
|
|
|
|
const findExecutable = (name, directories = getInstallDirectories()) => {
|
|
for (const directory of directories) {
|
|
const candidate = path.join(directory, name);
|
|
if (fs.existsSync(candidate)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const findOnPath = name => {
|
|
const pathEntries = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
|
|
|
|
for (const entry of pathEntries) {
|
|
const candidate = path.join(entry, name);
|
|
if (fs.existsSync(candidate)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const getNrfjprogExe = () => findExecutable('nrfjprog.exe') || findOnPath('nrfjprog.exe');
|
|
|
|
const getJLinkExe = () => {
|
|
const direct = findExecutable('JLink.exe') || findOnPath('JLink.exe');
|
|
if (direct) return direct;
|
|
|
|
const nrfjprogExe = getNrfjprogExe();
|
|
if (nrfjprogExe) {
|
|
const sibling = path.join(path.dirname(nrfjprogExe), 'JLink.exe');
|
|
if (fs.existsSync(sibling)) return sibling;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const getJLinkGdbServerExe = () => {
|
|
const candidates = ['JLinkGDBServerCL.exe', 'JLinkGDBServer.exe'];
|
|
for (const name of candidates) {
|
|
const direct = findExecutable(name) || findOnPath(name);
|
|
if (direct) return direct;
|
|
}
|
|
|
|
const nrfjprogExe = getNrfjprogExe();
|
|
if (nrfjprogExe) {
|
|
const directory = path.dirname(nrfjprogExe);
|
|
for (const name of candidates) {
|
|
const sibling = path.join(directory, name);
|
|
if (fs.existsSync(sibling)) return sibling;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const hasSeggerRttStack = () => Boolean(getJLinkGdbServerExe());
|
|
|
|
let cachedDiagnostics;
|
|
|
|
const clearJLinkDiagnosticsCache = () => {
|
|
cachedDiagnostics = null;
|
|
};
|
|
|
|
const getJLinkDiagnostics = () => {
|
|
if (cachedDiagnostics) return cachedDiagnostics;
|
|
|
|
let nrfjprogIds = [];
|
|
const nrfjprogExe = getNrfjprogExe();
|
|
const jlinkExe = getJLinkExe();
|
|
const gdbServerExe = getJLinkGdbServerExe();
|
|
|
|
if (nrfjprogExe) {
|
|
try {
|
|
const output = execFileSync(nrfjprogExe, ['--ids'], {
|
|
encoding: 'utf8',
|
|
timeout: 10000,
|
|
windowsHide: true,
|
|
});
|
|
nrfjprogIds = output
|
|
.split(/\r?\n/)
|
|
.map(line => line.trim())
|
|
.filter(line => /^\d+$/.test(line));
|
|
} catch {
|
|
nrfjprogIds = [];
|
|
}
|
|
}
|
|
|
|
cachedDiagnostics = {
|
|
nrfjprogExe,
|
|
jlinkExe,
|
|
gdbServerExe,
|
|
gdbServerDir: gdbServerExe ? path.dirname(gdbServerExe) : '',
|
|
nrfjprogIds,
|
|
hasRttStack: Boolean(gdbServerExe),
|
|
installDirectories: getInstallDirectories(),
|
|
};
|
|
|
|
return cachedDiagnostics;
|
|
};
|
|
|
|
module.exports = {
|
|
STATIC_INSTALL_DIRS,
|
|
getInstallDirectories,
|
|
discoverSeggerJLinkDirs,
|
|
findExecutable,
|
|
findOnPath,
|
|
getNrfjprogExe,
|
|
getJLinkExe,
|
|
getJLinkGdbServerExe,
|
|
hasSeggerRttStack,
|
|
getJLinkDiagnostics,
|
|
clearJLinkDiagnosticsCache,
|
|
};
|