From 4966d2384e68dffacecd3109e83a52ac2072a974 Mon Sep 17 00:00:00 2001 From: Julien Vanier Date: Fri, 7 Jul 2023 13:48:28 -0400 Subject: [PATCH 1/6] Remove populateFileMapping --- src/cmd/cloud.js | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/cmd/cloud.js b/src/cmd/cloud.js index 4a77e1df8..bb0e5bbe9 100644 --- a/src/cmd/cloud.js +++ b/src/cmd/cloud.js @@ -152,9 +152,7 @@ module.exports = class CloudCommand extends CLICommandBase { return Promise.resolve() .then(() => { - const fileMapping = this._handleMultiFileArgs(files, { followSymlinks }); - populateFileMapping(fileMapping); - return fileMapping; + return this._handleMultiFileArgs(files, { followSymlinks }); }) .then((fileMapping) => { if (Object.keys(fileMapping.map).length === 0){ @@ -246,7 +244,12 @@ module.exports = class CloudCommand extends CLICommandBase { if (specs){ if (specs.knownApps[filePath]){ - return populateFileMapping({ list: [specs.knownApps[filePath]] }); + const app = specs.knownApps[filePath]; + return { + map: { + [app]: app + } + }; } if (specs.productName){ @@ -304,8 +307,8 @@ module.exports = class CloudCommand extends CLICommandBase { } else { throw new Error([ `Target device ${deviceType} is not valid`, - ' eg. particle compile core xxx', - ' eg. particle compile photon xxx' + ' eg. particle compile boron xxx', + ' eg. particle compile p2 xxx' ].join('\n')); } @@ -908,16 +911,3 @@ function ensureAPIToken(){ throw new Error(`You're not logged in. Please login using ${chalk.bold.cyan('particle cloud login')} before using this command`); } } - -function populateFileMapping(fileMapping){ - if (!fileMapping.map){ - fileMapping.map = {}; - if (fileMapping.list){ - for (let i = 0; i < fileMapping.list.length; i++){ - let item = fileMapping.list[i]; - fileMapping.map[item] = item; - } - } - } - return fileMapping; -} From c57238ce281abfee4d5c92e68d1d436bde2ffcba Mon Sep 17 00:00:00 2001 From: Julien Vanier Date: Fri, 7 Jul 2023 17:18:47 -0400 Subject: [PATCH 2/6] Rework cloud flash to async await --- src/cmd/base.js | 8 +- src/cmd/cloud.js | 204 ++++++++++++++++++++++------------------------- 2 files changed, 98 insertions(+), 114 deletions(-) diff --git a/src/cmd/base.js b/src/cmd/base.js index a8affd256..44674eeed 100644 --- a/src/cmd/base.js +++ b/src/cmd/base.js @@ -22,12 +22,12 @@ module.exports = class CLICommandBase { return DEVICE_ID_PTN.test(x); } - showUsageError(msg){ - return Promise.reject(usageError(msg)); + async showUsageError(msg){ + throw usageError(msg); } - showProductDeviceNameUsageError(device){ - return this.showUsageError( + async showProductDeviceNameUsageError(device){ + await this.showUsageError( `\`device\` must be an id when \`--product\` flag is set - received: ${device}` ); } diff --git a/src/cmd/cloud.js b/src/cmd/cloud.js index bb0e5bbe9..622794b23 100644 --- a/src/cmd/cloud.js +++ b/src/cmd/cloud.js @@ -125,143 +125,127 @@ module.exports = class CloudCommand extends CLICommandBase { }); } - flashDevice({ target, followSymlinks, product, params: { device, files } }){ + async flashDevice({ target, followSymlinks, product, params: { device, files } }){ if (product){ if (!this.isDeviceId(device)){ - return this.showProductDeviceNameUsageError(device); + await this.showProductDeviceNameUsageError(device); } } - return Promise.resolve() - .then(() => { - if (files.length === 0){ - // default to current directory - files.push('.'); - } + try { + if (files.length === 0) { + // default to current directory + files.push('.'); + } - if (!fs.existsSync(files[0])){ - return this._flashKnownApp({ product, deviceId: device, filePath: files[0] }); - } + if (!await fs.exists(files[0])) { + await this._flashKnownApp({ product, deviceId: device, filePath: files[0] }); + return; + } - const targetVersion = target === 'latest' ? null : target; + const targetVersion = target === 'latest' ? null : target; - if (targetVersion){ - this.ui.stdout.write(`Targeting version: ${targetVersion}${os.EOL}`); - this.ui.stdout.write(os.EOL); - } + if (targetVersion) { + this.ui.stdout.write(`Targeting version: ${targetVersion}${os.EOL}`); + this.ui.stdout.write(os.EOL); + } - return Promise.resolve() - .then(() => { - return this._handleMultiFileArgs(files, { followSymlinks }); - }) - .then((fileMapping) => { - if (Object.keys(fileMapping.map).length === 0){ - throw new Error('no files included'); - } + const fileMapping = await this._handleMultiFileArgs(files, { followSymlinks }); + if (Object.keys(fileMapping.map).length === 0) { + throw new Error('no files included'); + } - if (settings.showIncludedSourceFiles){ - const list = _.values(fileMapping.map); + if (settings.showIncludedSourceFiles) { + const list = _.values(fileMapping.map); - this.ui.stdout.write(`Including:${os.EOL}`); + this.ui.stdout.write(`Including:${os.EOL}`); - for (let i = 0, n = list.length; i < n; i++){ - this.ui.stdout.write(` ${list[i]}${os.EOL}`); - } + for (let i = 0, n = list.length; i < n; i++) { + this.ui.stdout.write(` ${list[i]}${os.EOL}`); + } - this.ui.stdout.write(os.EOL); - } + this.ui.stdout.write(os.EOL); + } - return this._doFlash({ product, deviceId: device, fileMapping, targetVersion }); - }); - }) - .catch((error) => { - const message = `Failed to flash ${device}`; - throw createAPIErrorResult({ error, message }); - }); + await this._doFlash({ product, deviceId: device, fileMapping, targetVersion }); + } catch (error) { + const message = `Failed to flash ${device}`; + throw createAPIErrorResult({ error, message }); + } } - _doFlash({ product, deviceId, fileMapping, targetVersion }){ - return Promise.resolve() - .then(() => { - if (!product){ - return; - } - this.ui.stdout.write(`marking device ${deviceId} as a development device${os.EOL}`); - return createAPI().markAsDevelopmentDevice(deviceId, true, product); - }) - .then(() => { - this.ui.logFirstTimeFlashWarning(); - this.ui.stdout.write(`attempting to flash firmware to your device ${deviceId}${os.EOL}`); - return createAPI().flashDevice(deviceId, fileMapping, targetVersion, product); - }) - .then((resp) => { - if (resp.status || resp.message){ - this.ui.stdout.write(`Flash device OK: ${resp.status || resp.message}${os.EOL}`); - } else if (resp.output === 'Compiler timed out or encountered an error'){ - this.ui.stdout.write(`${os.EOL}${(resp.errors && resp.errors[0])}${os.EOL}`); - throw new Error('Compiler encountered an error'); - } else { - throw normalizedApiError(resp); - } - }) - .then(() => { - if (!product){ - return; - } - [ - `device ${deviceId} is now marked as a developement device and will NOT receive automatic product firmware updates.`, - 'to resume normal updates, please visit:', - // TODO (mirande): replace w/ instructions on how to unmark - // via the CLI once that command is available - `https://console.particle.io/${product}/devices/unmark-development/${deviceId}` - ].forEach(line => this.ui.stdout.write(`${line}${os.EOL}`)); - }) - .catch(err => { - throw normalizedApiError(err); - }); + async _doFlash({ product, deviceId, fileMapping, targetVersion }){ + try { + if (!product) { + return; + } + this.ui.stdout.write(`marking device ${deviceId} as a development device${os.EOL}`); + await createAPI().markAsDevelopmentDevice(deviceId, true, product); + + this.ui.logFirstTimeFlashWarning(); + this.ui.stdout.write(`attempting to flash firmware to your device ${deviceId}${os.EOL}`); + + const resp = await createAPI().flashDevice(deviceId, fileMapping, targetVersion, product); + if (resp.status || resp.message) { + this.ui.stdout.write(`Flash device OK: ${resp.status || resp.message}${os.EOL}`); + } else if (resp.output === 'Compiler timed out or encountered an error') { + this.ui.stdout.write(`${os.EOL}${(resp.errors && resp.errors[0])}${os.EOL}`); + throw new Error('Compiler encountered an error'); + } else { + throw normalizedApiError(resp); + } + + if (!product) { + return; + } + + [ + `device ${deviceId} is now marked as a developement device and will NOT receive automatic product firmware updates.`, + 'to resume normal updates, please visit:', + // TODO (mirande): replace w/ instructions on how to unmark + // via the CLI once that command is available + `https://console.particle.io/${product}/devices/unmark-development/${deviceId}` + ].forEach(line => this.ui.stdout.write(`${line}${os.EOL}`)); + } catch (err) { + throw normalizedApiError(err); + } } - _flashKnownApp({ product, deviceId, filePath }){ + async _flashKnownApp({ product, deviceId, filePath }){ if (!settings.cloudKnownApps[filePath]){ throw new VError(`I couldn't find that file: ${filePath}`); } - return createAPI().getDeviceAttributes(deviceId) - .then((attrs) => { - let productId = attrs.platform_id; // b/c legacy naming + const attrs = await createAPI().getDeviceAttributes(deviceId); + let productId = attrs.platform_id; // b/c legacy naming - if (product || attrs.platform_id !== attrs.product_id){ - if (!product){ - product = attrs.product_id; - } + if (product || attrs.platform_id !== attrs.product_id){ + if (!product){ + product = attrs.product_id; + } - if (!this.isDeviceId(deviceId)){ - deviceId = attrs.id; - } - } + if (!this.isDeviceId(deviceId)){ + deviceId = attrs.id; + } + } - const specs = _.find(deviceSpecs, { productId }); + let fileMapping; + const specs = _.find(deviceSpecs, { productId }); - if (specs){ - if (specs.knownApps[filePath]){ - const app = specs.knownApps[filePath]; - return { - map: { - [app]: app - } - }; - } + if (specs){ + if (specs.knownApps[filePath]){ + const app = specs.knownApps[filePath]; + fileMapping = { map: { [app]: app } }; + } - if (specs.productName){ - throw new VError(`I don't have a ${filePath} binary for ${specs.productName}.`); - } - } else { - throw new Error(`Unable to find ${filePath} for platform ${productId}`); - } - }) - .then((fileMapping) => { - return this._doFlash({ product, deviceId, fileMapping }); - }); + if (specs.productName){ + throw new VError(`I don't have a ${filePath} binary for ${specs.productName}.`); + } + } else { + throw new Error(`Unable to find ${filePath} for platform ${productId}`); + } + + await this._doFlash({ product, deviceId, fileMapping }); } _getDownloadPathForBin(deviceType, saveTo){ From 5e4967f41caea8d6341bdb392d5d05c18c45ecbd Mon Sep 17 00:00:00 2001 From: Julien Vanier Date: Fri, 7 Jul 2023 17:49:29 -0400 Subject: [PATCH 3/6] Reuse cloud compile as part of cloud flash command --- src/cmd/cloud.js | 72 ++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/src/cmd/cloud.js b/src/cmd/cloud.js index 622794b23..22e3dd22d 100644 --- a/src/cmd/cloud.js +++ b/src/cmd/cloud.js @@ -17,6 +17,7 @@ const fs = require('fs-extra'); const path = require('path'); const extend = require('xtend'); const chalk = require('chalk'); +const temp = require('temp').track(); const { ssoLogin, waitForLogin, getLoginMessage } = require('../lib/sso'); const BundleCommands = require('./bundle'); @@ -25,6 +26,7 @@ const alert = chalk.yellow('!'); // Use known platforms and add shortcuts const PLATFORMS = utilities.knownPlatformIdsWithAliases(); +const PLATFORMS_ID_TO_NAME = _.invert(utilities.knownPlatformIds()); module.exports = class CloudCommand extends CLICommandBase { constructor(...args){ @@ -143,31 +145,16 @@ module.exports = class CloudCommand extends CLICommandBase { return; } - const targetVersion = target === 'latest' ? null : target; + const attrs = await createAPI().getDeviceAttributes(device); + let platformId = attrs.platform_id; + const deviceType = PLATFORMS_ID_TO_NAME[platformId]; + const saveTo = temp.path({ suffix: '.zip' }); // compileCodeImpl will pick between .bin and .zip as appropriate - if (targetVersion) { - this.ui.stdout.write(`Targeting version: ${targetVersion}${os.EOL}`); - this.ui.stdout.write(os.EOL); - } - - const fileMapping = await this._handleMultiFileArgs(files, { followSymlinks }); - if (Object.keys(fileMapping.map).length === 0) { - throw new Error('no files included'); - } + const compiledFilename = await this.compileCodeImpl({ target, followSymlinks, saveTo, params: { deviceType, files } }); - if (settings.showIncludedSourceFiles) { - const list = _.values(fileMapping.map); - - this.ui.stdout.write(`Including:${os.EOL}`); - - for (let i = 0, n = list.length; i < n; i++) { - this.ui.stdout.write(` ${list[i]}${os.EOL}`); - } + const fileMapping = { map: { [compiledFilename]: compiledFilename } }; - this.ui.stdout.write(os.EOL); - } - - await this._doFlash({ product, deviceId: device, fileMapping, targetVersion }); + await this._doFlash({ product, deviceId: device, fileMapping }); } catch (error) { const message = `Failed to flash ${device}`; throw createAPIErrorResult({ error, message }); @@ -176,11 +163,10 @@ module.exports = class CloudCommand extends CLICommandBase { async _doFlash({ product, deviceId, fileMapping, targetVersion }){ try { - if (!product) { - return; + if (product) { + this.ui.stdout.write(`marking device ${deviceId} as a development device${os.EOL}`); + await createAPI().markAsDevelopmentDevice(deviceId, true, product); } - this.ui.stdout.write(`marking device ${deviceId} as a development device${os.EOL}`); - await createAPI().markAsDevelopmentDevice(deviceId, true, product); this.ui.logFirstTimeFlashWarning(); this.ui.stdout.write(`attempting to flash firmware to your device ${deviceId}${os.EOL}`); @@ -195,17 +181,15 @@ module.exports = class CloudCommand extends CLICommandBase { throw normalizedApiError(resp); } - if (!product) { - return; + if (product) { + [ + `device ${deviceId} is now marked as a developement device and will NOT receive automatic product firmware updates.`, + 'to resume normal updates, please visit:', + // TODO (mirande): replace w/ instructions on how to unmark + // via the CLI once that command is available + `https://console.particle.io/${product}/devices/unmark-development/${deviceId}` + ].forEach(line => this.ui.stdout.write(`${line}${os.EOL}`)); } - - [ - `device ${deviceId} is now marked as a developement device and will NOT receive automatic product firmware updates.`, - 'to resume normal updates, please visit:', - // TODO (mirande): replace w/ instructions on how to unmark - // via the CLI once that command is available - `https://console.particle.io/${product}/devices/unmark-development/${deviceId}` - ].forEach(line => this.ui.stdout.write(`${line}${os.EOL}`)); } catch (err) { throw normalizedApiError(err); } @@ -217,7 +201,7 @@ module.exports = class CloudCommand extends CLICommandBase { } const attrs = await createAPI().getDeviceAttributes(deviceId); - let productId = attrs.platform_id; // b/c legacy naming + let platformId = attrs.platform_id; if (product || attrs.platform_id !== attrs.product_id){ if (!product){ @@ -230,7 +214,7 @@ module.exports = class CloudCommand extends CLICommandBase { } let fileMapping; - const specs = _.find(deviceSpecs, { productId }); + const specs = _.find(deviceSpecs, { productId: platformId }); // b/c legacy naming if (specs){ if (specs.knownApps[filePath]){ @@ -242,7 +226,7 @@ module.exports = class CloudCommand extends CLICommandBase { throw new VError(`I don't have a ${filePath} binary for ${specs.productName}.`); } } else { - throw new Error(`Unable to find ${filePath} for platform ${productId}`); + throw new Error(`Unable to find ${filePath} for platform ${platformId}`); } await this._doFlash({ product, deviceId, fileMapping }); @@ -357,7 +341,7 @@ module.exports = class CloudCommand extends CLICommandBase { let filename = this._getDownloadPathForBin(deviceType, saveTo); const bundleFilename = this._getBundleSavePath(deviceType, saveTo, assets); - await this._compileAndDownload(fileMapping, platformId, filename, targetVersion, assets, bundleFilename); + return this._compileAndDownload(fileMapping, platformId, filename, targetVersion, assets, bundleFilename); } async _compileAndDownload(fileMapping, platformId, filename, targetVersion, assets, bundleFilename){ @@ -400,16 +384,20 @@ module.exports = class CloudCommand extends CLICommandBase { this.ui.stdout.write(`${respSizeInfo}${os.EOL}`); } + let compiledFilename; if (bundle) { if (await fs.exists(filename)){ await fs.unlink(filename); } + compiledFilename = path.resolve(bundleFilename); this.ui.stdout.write(`Compile succeeded and bundle created.${os.EOL}`); - this.ui.stdout.write(`Saved bundle to: ${path.resolve(bundleFilename)}${os.EOL}`); + this.ui.stdout.write(`Saved bundle to: ${compiledFilename}${os.EOL}`); } else { + compiledFilename = path.resolve(filename); this.ui.stdout.write(`Compile succeeded.${os.EOL}`); - this.ui.stdout.write(`Saved firmware to: ${path.resolve(filename)}${os.EOL}`); + this.ui.stdout.write(`Saved firmware to: ${compiledFilename}${os.EOL}`); } + return compiledFilename; } login({ username, password, token, otp, sso } = {}){ From 6bacb6b719ec52814bf9b89709bb4946c10759a6 Mon Sep 17 00:00:00 2001 From: Julien Vanier Date: Tue, 11 Jul 2023 15:45:03 -0400 Subject: [PATCH 4/6] Rework the console output for particle flash --- src/cmd/cloud.js | 105 +++++++++++++++++++++++------------------------ src/cmd/flash.js | 2 +- 2 files changed, 53 insertions(+), 54 deletions(-) diff --git a/src/cmd/cloud.js b/src/cmd/cloud.js index 22e3dd22d..fc0d87387 100644 --- a/src/cmd/cloud.js +++ b/src/cmd/cloud.js @@ -140,19 +140,30 @@ module.exports = class CloudCommand extends CLICommandBase { files.push('.'); } - if (!await fs.exists(files[0])) { + const filename = files[0]; + + if (!await fs.exists(filename)) { await this._flashKnownApp({ product, deviceId: device, filePath: files[0] }); return; } - const attrs = await createAPI().getDeviceAttributes(device); - let platformId = attrs.platform_id; - const deviceType = PLATFORMS_ID_TO_NAME[platformId]; - const saveTo = temp.path({ suffix: '.zip' }); // compileCodeImpl will pick between .bin and .zip as appropriate + let fileMapping; + // check if extension is .bin or .zip + const ext = path.extname(filename); + if (['.bin', '.zip'].includes(ext)) { + fileMapping = { map: { [filename]: filename } }; + } else { + this.ui.stdout.write(`Compiling code for ${device}${os.EOL}`); + + const attrs = await createAPI().getDeviceAttributes(device); + let platformId = attrs.platform_id; + const deviceType = PLATFORMS_ID_TO_NAME[platformId]; + const saveTo = temp.path({ suffix: '.zip' }); // compileCodeImpl will pick between .bin and .zip as appropriate - const compiledFilename = await this.compileCodeImpl({ target, followSymlinks, saveTo, params: { deviceType, files } }); + const compiledFilename = await this.compileCodeImpl({ target, followSymlinks, saveTo, deviceType, platformId, files }); - const fileMapping = { map: { [compiledFilename]: compiledFilename } }; + fileMapping = { map: { [compiledFilename]: compiledFilename } }; + } await this._doFlash({ product, deviceId: device, fileMapping }); } catch (error) { @@ -164,26 +175,21 @@ module.exports = class CloudCommand extends CLICommandBase { async _doFlash({ product, deviceId, fileMapping, targetVersion }){ try { if (product) { - this.ui.stdout.write(`marking device ${deviceId} as a development device${os.EOL}`); + this.ui.stdout.write(`Marking device ${deviceId} as a development device${os.EOL}`); await createAPI().markAsDevelopmentDevice(deviceId, true, product); } this.ui.logFirstTimeFlashWarning(); - this.ui.stdout.write(`attempting to flash firmware to your device ${deviceId}${os.EOL}`); + this.ui.stdout.write(`Flashing firmware to your device ${deviceId}${os.EOL}`); const resp = await createAPI().flashDevice(deviceId, fileMapping, targetVersion, product); - if (resp.status || resp.message) { - this.ui.stdout.write(`Flash device OK: ${resp.status || resp.message}${os.EOL}`); - } else if (resp.output === 'Compiler timed out or encountered an error') { - this.ui.stdout.write(`${os.EOL}${(resp.errors && resp.errors[0])}${os.EOL}`); - throw new Error('Compiler encountered an error'); - } else { + if (!resp.status && !resp.message) { throw normalizedApiError(resp); } if (product) { [ - `device ${deviceId} is now marked as a developement device and will NOT receive automatic product firmware updates.`, + `Device ${deviceId} is now marked as a developement device and will NOT receive automatic product firmware updates.`, 'to resume normal updates, please visit:', // TODO (mirande): replace w/ instructions on how to unmark // via the CLI once that command is available @@ -220,9 +226,7 @@ module.exports = class CloudCommand extends CLICommandBase { if (specs.knownApps[filePath]){ const app = specs.knownApps[filePath]; fileMapping = { map: { [app]: app } }; - } - - if (specs.productName){ + } else { throw new VError(`I don't have a ${filePath} binary for ${specs.productName}.`); } } else { @@ -255,32 +259,36 @@ module.exports = class CloudCommand extends CLICommandBase { // create a new function that handles errors from compileCode function async compileCode({ target, followSymlinks, saveTo, params: { deviceType, files } }){ try { - return await this.compileCodeImpl({ target, followSymlinks, saveTo, params: { deviceType, files } }); + if (files.length === 0) { + files.push('.'); // default to current directory + } + + let platformId; + if (deviceType in PLATFORMS) { + platformId = PLATFORMS[deviceType]; + } else { + throw new Error([ + `Target device ${deviceType} is not valid`, + ' eg. particle compile boron xxx', + ' eg. particle compile p2 xxx' + ].join('\n')); + } + + this.ui.stdout.write(`Compiling code for ${deviceType}${os.EOL}`); + + const compiledFilename = await this.compileCodeImpl({ target, followSymlinks, saveTo, deviceType, platformId, files, showMemoryStats: true }); + + this.ui.stdout.write(`Saved firmware to: ${compiledFilename}${os.EOL}`); } catch (error) { const message = 'Compile failed'; throw createAPIErrorResult({ error, message }); } } - async compileCodeImpl({ target, followSymlinks, saveTo, params: { deviceType, files } }) { - let platformId, targetVersion, assets; + async compileCodeImpl({ target, followSymlinks, saveTo, deviceType, platformId, files, showMemoryStats = false }) { + let targetVersion, assets; ensureAPIToken(); - if (files.length === 0) { - files.push('.'); // default to current directory - } - - if (deviceType in PLATFORMS) { - platformId = PLATFORMS[deviceType]; - } else { - throw new Error([ - `Target device ${deviceType} is not valid`, - ' eg. particle compile boron xxx', - ' eg. particle compile p2 xxx' - ].join('\n')); - } - - this.ui.stdout.write(`${os.EOL}Compiling code for ${deviceType}${os.EOL}`); if (target) { if (target === 'latest') { @@ -341,13 +349,12 @@ module.exports = class CloudCommand extends CLICommandBase { let filename = this._getDownloadPathForBin(deviceType, saveTo); const bundleFilename = this._getBundleSavePath(deviceType, saveTo, assets); - return this._compileAndDownload(fileMapping, platformId, filename, targetVersion, assets, bundleFilename); + return this._compileAndDownload({ fileMapping, platformId, filename, targetVersion, assets, bundleFilename, showMemoryStats }); } - async _compileAndDownload(fileMapping, platformId, filename, targetVersion, assets, bundleFilename){ + async _compileAndDownload({ fileMapping, platformId, filename, targetVersion, assets, bundleFilename, showMemoryStats }){ let respSizeInfo, bundle, resp; - this.ui.stdout.write(`attempting to compile firmware${os.EOL}`); try { resp = await createAPI().compileCode(fileMapping, platformId, targetVersion); } catch (error) { @@ -355,7 +362,6 @@ module.exports = class CloudCommand extends CLICommandBase { } if (resp && resp.binary_url && resp.binary_id) { - this.ui.stdout.write(`downloading binary from: ${resp.binary_url}${os.EOL}`); let data; try { data = await createAPI().downloadFirmwareBinary(resp.binary_id); @@ -363,9 +369,6 @@ module.exports = class CloudCommand extends CLICommandBase { throw normalizedApiError(error); } - if (!assets) { - this.ui.stdout.write(`saving to: ${filename}${os.EOL}`); - } await fs.writeFile(filename, data); respSizeInfo = resp.sizeInfo; } else if (resp && resp.output === 'Compiler timed out or encountered an error'){ @@ -375,29 +378,25 @@ module.exports = class CloudCommand extends CLICommandBase { throw normalizedApiError(resp); } + this.ui.stdout.write(`Compile succeeded.${os.EOL}${os.EOL}`); + if (assets) { bundle = await new BundleCommands()._generateBundle({ assetsList: assets, appBinary: filename, bundleFilename: bundleFilename }); } - if (respSizeInfo){ + if (showMemoryStats && respSizeInfo){ this.ui.stdout.write(`Memory use:${os.EOL}`); this.ui.stdout.write(`${respSizeInfo}${os.EOL}`); } - let compiledFilename; if (bundle) { if (await fs.exists(filename)){ await fs.unlink(filename); } - compiledFilename = path.resolve(bundleFilename); - this.ui.stdout.write(`Compile succeeded and bundle created.${os.EOL}`); - this.ui.stdout.write(`Saved bundle to: ${compiledFilename}${os.EOL}`); + return path.resolve(bundleFilename); } else { - compiledFilename = path.resolve(filename); - this.ui.stdout.write(`Compile succeeded.${os.EOL}`); - this.ui.stdout.write(`Saved firmware to: ${compiledFilename}${os.EOL}`); + return path.resolve(filename); } - return compiledFilename; } login({ username, password, token, otp, sso } = {}){ diff --git a/src/cmd/flash.js b/src/cmd/flash.js index 22ec3a732..0f0a6107c 100644 --- a/src/cmd/flash.js +++ b/src/cmd/flash.js @@ -30,7 +30,7 @@ module.exports = class FlashCommand extends CLICommandBase { } return result.then(() => { - this.ui.write('\nFlash success!'); + this.ui.write('Flash success!'); }); } From c46118026ac4f8dc0cbd1a3a4609dc1cc4f1a869 Mon Sep 17 00:00:00 2001 From: Julien Vanier Date: Wed, 12 Jul 2023 12:02:23 -0400 Subject: [PATCH 5/6] Display the memory stats in human readable format --- src/cmd/cloud.js | 52 +++++++++++++++++++++++++++++++++++++------ src/cmd/cloud.test.js | 30 +++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/cmd/cloud.js b/src/cmd/cloud.js index fc0d87387..c3839eabe 100644 --- a/src/cmd/cloud.js +++ b/src/cmd/cloud.js @@ -276,7 +276,7 @@ module.exports = class CloudCommand extends CLICommandBase { this.ui.stdout.write(`Compiling code for ${deviceType}${os.EOL}`); - const compiledFilename = await this.compileCodeImpl({ target, followSymlinks, saveTo, deviceType, platformId, files, showMemoryStats: true }); + const compiledFilename = await this.compileCodeImpl({ target, followSymlinks, saveTo, deviceType, platformId, files }); this.ui.stdout.write(`Saved firmware to: ${compiledFilename}${os.EOL}`); } catch (error) { @@ -285,7 +285,7 @@ module.exports = class CloudCommand extends CLICommandBase { } } - async compileCodeImpl({ target, followSymlinks, saveTo, deviceType, platformId, files, showMemoryStats = false }) { + async compileCodeImpl({ target, followSymlinks, saveTo, deviceType, platformId, files }) { let targetVersion, assets; ensureAPIToken(); @@ -349,10 +349,10 @@ module.exports = class CloudCommand extends CLICommandBase { let filename = this._getDownloadPathForBin(deviceType, saveTo); const bundleFilename = this._getBundleSavePath(deviceType, saveTo, assets); - return this._compileAndDownload({ fileMapping, platformId, filename, targetVersion, assets, bundleFilename, showMemoryStats }); + return this._compileAndDownload({ fileMapping, platformId, filename, targetVersion, assets, bundleFilename }); } - async _compileAndDownload({ fileMapping, platformId, filename, targetVersion, assets, bundleFilename, showMemoryStats }){ + async _compileAndDownload({ fileMapping, platformId, filename, targetVersion, assets, bundleFilename }){ let respSizeInfo, bundle, resp; try { @@ -384,9 +384,8 @@ module.exports = class CloudCommand extends CLICommandBase { bundle = await new BundleCommands()._generateBundle({ assetsList: assets, appBinary: filename, bundleFilename: bundleFilename }); } - if (showMemoryStats && respSizeInfo){ - this.ui.stdout.write(`Memory use:${os.EOL}`); - this.ui.stdout.write(`${respSizeInfo}${os.EOL}`); + if (respSizeInfo){ + this._showMemoryStats(respSizeInfo); } if (bundle) { @@ -399,6 +398,45 @@ module.exports = class CloudCommand extends CLICommandBase { } } + _showMemoryStats(sizeInfo) { + const stats = this._parseMemoryStats(sizeInfo); + if (stats) { + const rightAlign = (str, len) => `${' '.repeat(len - str.length)}${str}`; + + this.ui.stdout.write(`Memory use:${os.EOL}`); + this.ui.stdout.write(rightAlign('Flash', 9) + rightAlign('RAM', 9) + os.EOL); + this.ui.stdout.write(rightAlign(stats.flash.toString(), 9) + rightAlign(stats.ram.toString(), 9) + os.EOL); + this.ui.stdout.write(os.EOL); + } + } + + _parseMemoryStats(sizeInfo) { + if (!sizeInfo) { + return null; + } + const lines = sizeInfo.split('\n'); + if (lines.length < 2) { + return null; + } + + const fields = lines[0].replace(/^\s+/, '').split(/\s+/); + const values = lines[1].replace(/^\s+/, '').split(/\s+/); + + const sizes = {}; + for (let i = 0; i < fields.length && i < 4; i++) { + sizes[fields[i]] = parseInt(values[i], 10); + } + + if (!('text' in sizes && 'data' in sizes && 'bss' in sizes)) { + return null; + } + + return { + flash: sizes.text + sizes.data, // text is code, data is the constant data + ram: sizes.bss + sizes.data // bss is non-initialized or 0 initialized ram, data is ram initialized from values in flash + }; + } + login({ username, password, token, otp, sso } = {}){ const shouldRetry = !((username && password) || (token || sso) && !this.tries); diff --git a/src/cmd/cloud.test.js b/src/cmd/cloud.test.js index 2ef7b696b..3200cac55 100644 --- a/src/cmd/cloud.test.js +++ b/src/cmd/cloud.test.js @@ -885,6 +885,36 @@ describe('Cloud Commands', () => { }); }); + describe('_parseMemoryStats', () => { + let cloud; + beforeEach(() => { + cloud = new CloudCommands(); + }); + + it('parses memory stats', () => { + let statsText = ' text data bss dec hex filename\n' + + '3308 112 1356 4776 12a8 /workspace/target/workspace.elf'; + + const stats = cloud._parseMemoryStats(statsText); + + expect(stats).to.eql({ flash: 3308 + 112, ram: 1356 + 112 }); + }); + + it('returns null when stats are missing', () => { + const stats = cloud._parseMemoryStats(null); + + expect(stats).to.eql(null); + }); + + it('returns null when stats are invalid', () => { + let statsText = 'invalid'; + + const stats = cloud._parseMemoryStats(statsText); + + expect(stats).to.eql(null); + }); + }); + async function createTmpDir(files, fileContents, handler) { const tmpDir = path.join(PATH_TMP_DIR, 'tmpDir'); await fs.mkdir(tmpDir); From 8e2c6a21a1e8eaaae3e0d1358305c305e4f2049e Mon Sep 17 00:00:00 2001 From: Julien Vanier Date: Wed, 12 Jul 2023 14:11:47 -0400 Subject: [PATCH 6/6] Fix tests --- src/cmd/cloud.js | 24 +++++++++----- test/e2e/compile.e2e.js | 70 +++++++++++++---------------------------- 2 files changed, 37 insertions(+), 57 deletions(-) diff --git a/src/cmd/cloud.js b/src/cmd/cloud.js index c3839eabe..2912218a2 100644 --- a/src/cmd/cloud.js +++ b/src/cmd/cloud.js @@ -160,9 +160,9 @@ module.exports = class CloudCommand extends CLICommandBase { const deviceType = PLATFORMS_ID_TO_NAME[platformId]; const saveTo = temp.path({ suffix: '.zip' }); // compileCodeImpl will pick between .bin and .zip as appropriate - const compiledFilename = await this.compileCodeImpl({ target, followSymlinks, saveTo, deviceType, platformId, files }); + const { filename } = await this.compileCodeImpl({ target, followSymlinks, saveTo, deviceType, platformId, files }); - fileMapping = { map: { [compiledFilename]: compiledFilename } }; + fileMapping = { map: { [filename]: filename } }; } await this._doFlash({ product, deviceId: device, fileMapping }); @@ -276,9 +276,9 @@ module.exports = class CloudCommand extends CLICommandBase { this.ui.stdout.write(`Compiling code for ${deviceType}${os.EOL}`); - const compiledFilename = await this.compileCodeImpl({ target, followSymlinks, saveTo, deviceType, platformId, files }); + const { filename, isBundle } = await this.compileCodeImpl({ target, followSymlinks, saveTo, deviceType, platformId, files }); - this.ui.stdout.write(`Saved firmware to: ${compiledFilename}${os.EOL}`); + this.ui.stdout.write(`Saved ${isBundle ? 'bundle' : 'firmware' } to: ${filename}${os.EOL}`); } catch (error) { const message = 'Compile failed'; throw createAPIErrorResult({ error, message }); @@ -378,12 +378,14 @@ module.exports = class CloudCommand extends CLICommandBase { throw normalizedApiError(resp); } - this.ui.stdout.write(`Compile succeeded.${os.EOL}${os.EOL}`); - + let message = 'Compile succeeded.'; if (assets) { bundle = await new BundleCommands()._generateBundle({ assetsList: assets, appBinary: filename, bundleFilename: bundleFilename }); + message = 'Compile succeeded and bundle created.'; } + this.ui.stdout.write(`${message}${os.EOL}${os.EOL}`); + if (respSizeInfo){ this._showMemoryStats(respSizeInfo); } @@ -392,9 +394,15 @@ module.exports = class CloudCommand extends CLICommandBase { if (await fs.exists(filename)){ await fs.unlink(filename); } - return path.resolve(bundleFilename); + return { + isBundle: true, + filename: path.resolve(bundleFilename) + }; } else { - return path.resolve(filename); + return { + isBundle: false, + filename: path.resolve(filename) + }; } } diff --git a/test/e2e/compile.e2e.js b/test/e2e/compile.e2e.js index f6ef2e030..ee28b4f5a 100644 --- a/test/e2e/compile.e2e.js +++ b/test/e2e/compile.e2e.js @@ -76,9 +76,7 @@ describe('Compile Commands', () => { '', 'Including:', ` ${PATH_PROJ_STROBY_INO}`, - 'attempting to compile firmware', '', // don't assert against binary info since it's always unique: e.g. 'downloading binary from: /v1/binaries/5d38f108bc91fb000130a3f9' - `saving to: ${strobyBinPath}`, 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version 'Compile succeeded.', @@ -102,12 +100,10 @@ describe('Compile Commands', () => { '', 'Including:', ` ${PATH_PROJ_STROBY_INO}`, - 'attempting to compile firmware', '', // don't assert against binary info since it's always unique: e.g. 'downloading binary from: /v1/binaries/5d38f108bc91fb000130a3f9' - `saving to: ${strobyBinPath}`, 'Memory use:', - ' text\t data\t bss\t dec\t hex\tfilename', - ' 8604\t 108\t 1136\t 9848\t 2678\t/workspace/target/workspace.elf', + ' Flash RAM', + ' 8712 1244', '', 'Compile succeeded.', `Saved firmware to: ${strobyBinPath}` @@ -132,13 +128,11 @@ describe('Compile Commands', () => { `Compiling code for ${platform}`, '', 'Including:', - ' helper.h', ' app.ino', ' helper.cpp', + ' helper.h', '', - 'attempting to compile firmware', '', // don't assert against memory stats since they may change based on current default Device OS version - `saving to: ${destination}`, 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version 'Compile succeeded.', @@ -164,15 +158,13 @@ describe('Compile Commands', () => { `Compiling code for ${platform}`, '', 'Including:', - ' lib/helper/src/helper.h', - ' src/app.ino', ' lib/helper/src/helper.cpp', - ' project.properties', ' lib/helper/src/helper.def', + ' lib/helper/src/helper.h', + ' project.properties', + ' src/app.ino', '', - 'attempting to compile firmware', '', // don't assert against memory stats since they may change based on current default Device OS version - `saving to: ${destination}`, 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version 'Compile succeeded.', @@ -198,14 +190,12 @@ describe('Compile Commands', () => { `Compiling code for ${platform}`, '', 'Including:', - ' lib/helper/src/helper.h', - ' src/app.ino', ' lib/helper/src/helper.cpp', + ' lib/helper/src/helper.h', ' project.properties', + ' src/app.ino', '', - 'attempting to compile firmware', '', // don't assert against memory stats since they may change based on current default Device OS version - `saving to: ${destination}`, 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version 'Compile succeeded.', @@ -235,9 +225,7 @@ describe('Compile Commands', () => { ' helper.cpp', ' helper.h', '', - 'attempting to compile firmware', '', // don't assert against memory stats since they may change based on current default Device OS version - `saving to: ${destination}`, 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version 'Compile succeeded.', @@ -265,13 +253,11 @@ describe('Compile Commands', () => { `Compiling code for ${platform}`, '', 'Including:', - ' helper.h', ' app.ino', ' helper.cpp', + ' helper.h', '', - 'attempting to compile firmware', '', // don't assert against memory stats since they may change based on current default Device OS version - `saving to: ${destination}`, 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version 'Compile succeeded.', @@ -298,19 +284,17 @@ describe('Compile Commands', () => { '', 'Including:', ' project.properties', - ' src/helper/h0.h', - ' src/helper/h1.hpp', - ' src/helper/h3.hh', - ' src/helper/h2.hxx', ' src/app.ino', ' src/helper/h0.cpp', + ' src/helper/h0.h', ' src/helper/h1.cpp', + ' src/helper/h1.hpp', ' src/helper/h2.cpp', + ' src/helper/h2.hxx', ' src/helper/h3.cpp', + ' src/helper/h3.hh', '', - 'attempting to compile firmware', '', // don't assert against memory stats since they may change based on current default Device OS version - `saving to: ${destination}`, 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version 'Compile succeeded.', @@ -335,10 +319,10 @@ describe('Compile Commands', () => { const log = [ `Compiling code for ${platform}`, 'Including:', - ' shared/sub_dir/helper.h', ' app.ino', - ' shared/sub_dir/helper.cpp', ' project.properties', + ' shared/sub_dir/helper.h', + ' shared/sub_dir/helper.cpp', ]; expect(file.size).to.be.above(minBinSize); @@ -360,13 +344,11 @@ describe('Compile Commands', () => { `Compiling code for ${platform}`, '', 'Including:', - ' legacy-flat/helper.h', ' legacy-flat/app.ino', ' legacy-flat/helper.cpp', + ' legacy-flat/helper.h', '', - 'attempting to compile firmware', '', // don't assert against memory stats since they may change based on current default Device OS version - `saving to: ${destination}`, 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version 'Compile succeeded.', @@ -392,14 +374,12 @@ describe('Compile Commands', () => { `Compiling code for ${platform}`, '', 'Including:', - ' src/helper/helper.h', ' src/app.ino', ' src/helper/helper.cpp', + ' src/helper/helper.h', ' project.properties', '', - 'attempting to compile firmware', '', // don't assert against memory stats since they may change based on current default Device OS version - `saving to: ${destination}`, 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version 'Compile succeeded.', @@ -432,9 +412,7 @@ describe('Compile Commands', () => { ' src/test-library-publish.h', ' src/uber-library-example/uber-library-example.h', '', - 'attempting to compile firmware', '', // don't assert against memory stats since they may change based on current default Device OS version - `saving to: ${destination}`, 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version 'Compile succeeded.', @@ -457,9 +435,7 @@ describe('Compile Commands', () => { '', 'Including:', ` ${PATH_PROJ_STROBY_INO}`, - 'attempting to compile firmware', '', // don't assert against binary info since it's always unique: e.g. 'downloading binary from: /v1/binaries/5d38f108bc91fb000130a3f9' - `saving to: ${strobyBinPath}`, 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version 'Compile succeeded.', @@ -479,8 +455,8 @@ describe('Compile Commands', () => { const { stdout, stderr, exitCode } = await cli.run(args, { cwd }); const log = [ 'Compile failed: Target device WATNOPE is not valid', - ' eg. particle compile core xxx', - ' eg. particle compile photon xxx' + ' eg. particle compile boron xxx', + ' eg. particle compile p2 xxx' ]; expect(stdout.split('\n')).to.include.members(log); @@ -513,13 +489,11 @@ describe('Compile Commands', () => { `Compiling code for ${platform}`, '', 'Including:', - ' helper.h', ' app.ino', ' helper.cpp', + ' helper.h', '', - 'attempting to compile firmware', '', // don't assert against memory stats since they may change based on current default Device OS version - `saving to: ${destination}`, `Compile failed: ENOENT: no such file or directory, open '${destination}'` ]; @@ -583,7 +557,6 @@ describe('Compile Commands', () => { ' assets/house.txt', ' assets/water.txt', '', - 'attempting to compile firmware', '', // don't assert against memory stats since they may change based on current default Device OS version 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version @@ -633,13 +606,12 @@ describe('Compile Commands', () => { `Compiling code for ${platform}`, '', 'Including:', - ' src/stroby.ino', ' project.properties', + ' src/stroby.ino', ' assets/cat.txt', ' assets/house.txt', ' assets/water.txt', '', - 'attempting to compile firmware', '', // don't assert against memory stats since they may change based on current default Device OS version 'Memory use:', '', // don't assert against memory stats since they may change based on current default Device OS version