Merge branch '0.25.x' into chore-merge-0.25.x

This commit is contained in:
Kirill Shumilov
2018-10-19 12:43:38 +03:00
24 changed files with 306 additions and 117 deletions

View File

@@ -36,6 +36,14 @@ Wraps `arduino-cli config dump`.
- Returns `Promise<Object>`
### updateConfig(newConfig)
Replaces old config with the new one.
Accepts:
- `newConfig` `<Object>` — Plain JS object representation of `.cli-config.yml`
- Returns `<Object>` with the new config
### listConnectedBoards()
A wrapper with a custom extension over `arduino-cli board list`.

View File

@@ -9,10 +9,8 @@ const getDefaultConfig = configDir => ({
arduino_data: resolve(configDir, 'data'),
});
export const configure = inputConfig => {
const configDir = fse.mkdtempSync(resolve(tmpdir(), 'arduino-cli'));
const configPath = resolve(configDir, '.cli-config.yml');
const config = inputConfig || getDefaultConfig(configDir);
// :: Path -> Object -> { config: Object, path: Path }
export const saveConfig = (configPath, config) => {
const yamlString = YAML.stringify(config, 2);
// Write config
@@ -28,6 +26,14 @@ export const configure = inputConfig => {
};
};
// :: Object -> { config: Object, path: Path }
export const configure = inputConfig => {
const configDir = fse.mkdtempSync(resolve(tmpdir(), 'arduino-cli'));
const configPath = resolve(configDir, '.cli-config.yml');
const config = inputConfig || getDefaultConfig(configDir);
return saveConfig(configPath, config);
};
// :: Path -> [URL] -> Promise [URL] Error
export const addPackageIndexUrls = (configPath, urls) =>
fse

View File

@@ -5,7 +5,12 @@ import { exec, spawn } from 'child-process-promise';
import YAML from 'yamljs';
import { remove } from 'fs-extra';
import { configure, addPackageIndexUrl, addPackageIndexUrls } from './config';
import {
saveConfig,
configure,
addPackageIndexUrl,
addPackageIndexUrls,
} from './config';
import { patchBoardsWithOptions } from './optionParser';
import listAvailableBoards from './listAvailableBoards';
import parseProgressLog from './parseProgressLog';
@@ -19,7 +24,7 @@ const escapeSpacesNonWin = R.unless(() => IS_WIN, R.replace(/\s/g, '\\ '));
* @param {Object} config Plain-object representation of `.cli-config.yml`
*/
const ArduinoCli = (pathToBin, config = null) => {
const { path: configPath, config: cfg } = configure(config);
let { path: configPath, config: cfg } = configure(config);
const escapedConfigPath = escapeSpacesNonWin(configPath);
const run = args =>
@@ -68,6 +73,12 @@ const ArduinoCli = (pathToBin, config = null) => {
return {
dumpConfig: getConfig,
updateConfig: newConfig => {
const newCfg = saveConfig(configPath, newConfig);
configPath = newCfg.path;
cfg = newCfg.config;
return cfg;
},
listConnectedBoards: () => listBoardsWith('list', R.prop('serialBoards')),
listInstalledBoards: () => listBoardsWith('listall', R.prop('boards')),
listAvailableBoards: () => listAvailableBoards(cfg.arduino_data),

View File

@@ -45,9 +45,9 @@ export const getLines = R.compose(
const menuRegExp = /^menu\./;
const optionNameRegExp = /^menu\.([a-zA-Z0-9_]+)=([a-zA-Z0-9-_ ]+)$/;
const optionNameRegExp = /^menu\.([a-zA-Z0-9_]+)=(.+)$/;
const boardOptionRegExp = /^([a-zA-Z0-9_]+)\.menu\.([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)=([a-zA-Z0-9-_ ()]+)$/;
const boardOptionRegExp = /^([a-zA-Z0-9_]+)\.menu\.([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)=(.+)$/;
const osRegExp = /(linux|macosx|windows)/;

View File

@@ -1,3 +1,4 @@
import * as R from 'ramda';
import { resolve } from 'path';
import * as fse from 'fs-extra';
import { assert } from 'chai';
@@ -42,6 +43,27 @@ describe('Arduino Cli', () => {
}));
});
describe('Update arduino-cli config', () => {
afterEach(() => fse.remove(tmpDir));
it('updates config', async () => {
const cli = arduinoCli(PATH_TO_CLI, cfg);
const curConf = await cli.dumpConfig();
assert.strictEqual(curConf.sketchbook_path, cfg.sketchbook_path);
assert.strictEqual(curConf.arduino_data, cfg.arduino_data);
const newDataDir = resolve(tmpDir, 'newData');
const newConf = R.assoc('arduino_data', newDataDir, curConf);
cli.updateConfig(newConf);
const updatedConf = await cli.dumpConfig();
assert.strictEqual(updatedConf.sketchbook_path, cfg.sketchbook_path);
assert.strictEqual(updatedConf.arduino_data, newDataDir);
return cli;
});
});
describe('Installs additional package index', () => {
afterEach(() => fse.remove(tmpDir));
const url =

View File

@@ -1,15 +1,13 @@
#### JUST A PART OF BOARDS.TXT FILE FROM ESP8266 PACKAGE
#### SOME OPTIONS WAS REMOVED TO MINIFY FIXTURE
#### A PART OF BOARDS.TXT FILE FROM ESP8266 PACKAGE
#### WITH SOME CHANGES: MENU AND OPTION NAMES
#### NOW CONTAINS SPECIAL CHARACTERS AND CYRILLIC SYMBOLS
#### TO MAKE SURE IN THE UNIT TESTS IT WILL WORK FINE
#### WITH ANY PACKAGE.
####
#### ONLY FOR TESTS!
#
# Do not create pull-requests for this file only, CI will not accept them.
# You *must* edit/modify/run boards.txt.py to regenerate boards.txt.
# All modified files after running with option "--allgen" must be included in the pull-request.
#
menu.UploadSpeed=Upload Speed
menu.CpuFrequency=CPU Frequency
menu.UploadSpeed=Uplo@d Speed (Скорость загрузки)
menu.CpuFrequency=CPU Frequency 123,-_[]():@we$ome
##############################################################
generic.name=Generic ESP8266 Module
@@ -26,7 +24,7 @@ generic.build.variant=generic
generic.build.spiffs_pagesize=256
generic.build.debug_port=
generic.build.debug_level=
generic.menu.CpuFrequency.80=80 MHz
generic.menu.CpuFrequency.80=80 MHz (!@#$$%^&*()[]_-+,./)
generic.menu.CpuFrequency.80.build.f_cpu=80000000L
generic.menu.CpuFrequency.160=160 MHz
generic.menu.CpuFrequency.160.build.f_cpu=160000000L
@@ -64,7 +62,7 @@ wifi_slot.build.core=esp8266
wifi_slot.build.spiffs_pagesize=256
wifi_slot.build.debug_port=
wifi_slot.build.debug_level=
wifi_slot.menu.CpuFrequency.80=80 MHz
wifi_slot.menu.CpuFrequency.80=80 MHz (!@#$$%^&*()[]_-+,./)
wifi_slot.menu.CpuFrequency.80.build.f_cpu=80000000L
wifi_slot.menu.CpuFrequency.160=160 MHz
wifi_slot.menu.CpuFrequency.160.build.f_cpu=160000000L

View File

@@ -22,8 +22,8 @@ const fixtureDir = path.resolve(__dirname, 'fixtures');
// =============================================================================
const espOptionNames = {
UploadSpeed: 'Upload Speed',
CpuFrequency: 'CPU Frequency',
UploadSpeed: 'Uplo@d Speed (Скорость загрузки)',
CpuFrequency: 'CPU Frequency 123,-_[]():@we$ome',
};
const espOptions = {
@@ -47,7 +47,7 @@ const espOptions = {
],
CpuFrequency: [
{
name: '80 MHz',
name: '80 MHz (!@#$$%^&*()[]_-+,./)',
value: '80',
},
{
@@ -58,7 +58,7 @@ const espOptions = {
};
const uploadSpeedOptions = {
optionName: 'Upload Speed',
optionName: 'Uplo@d Speed (Скорость загрузки)',
optionId: 'UploadSpeed',
values: [
{
@@ -80,11 +80,11 @@ const uploadSpeedOptions = {
],
};
const cpuFrequencyOptions = {
optionName: 'CPU Frequency',
optionName: 'CPU Frequency 123,-_[]():@we$ome',
optionId: 'CpuFrequency',
values: [
{
name: '80 MHz',
name: '80 MHz (!@#$$%^&*()[]_-+,./)',
value: '80',
},
{

View File

@@ -198,6 +198,22 @@ bool isTimedOut(const ContextT* ctx) {
return detail::isTimedOut(ctx->_node);
}
bool isValidDigitalPort(uint8_t port) {
#ifdef NUM_DIGITAL_PINS
return port < NUM_DIGITAL_PINS;
#else
return true;
#endif
}
bool isValidAnalogPort(uint8_t port) {
#ifdef NUM_ANALOG_INPUTS
return port >= A0 && port < A0 + NUM_ANALOG_INPUTS;
#else
return port >= A0;
#endif
}
} // namespace xod
//----------------------------------------------------------------------------

View File

@@ -97,12 +97,16 @@ const copyLibraries = async (bundledLibDir, userLibDir, sketchbookLibDir) => {
return sketchbookLibDir;
};
// :: _ -> Promise Path Error
const ensureExtraTxt = R.composeP(
extraFilePath => fse.ensureFile(extraFilePath).then(R.always(extraFilePath)),
ws => path.join(ws, ARDUINO_PACKAGES_DIRNAME, ARDUINO_EXTRA_URLS_FILENAME),
loadWorkspacePath
);
// :: Path -> Promise Path Error
const ensureExtraTxt = async wsPath => {
const extraTxtFilePath = path.join(
wsPath,
ARDUINO_PACKAGES_DIRNAME,
ARDUINO_EXTRA_URLS_FILENAME
);
await fse.ensureFile(extraTxtFilePath);
return extraTxtFilePath;
};
const copyPackageIndexes = async wsPackageDir => {
const filesToCopy = await R.composeP(
@@ -209,6 +213,30 @@ const patchFqbnWithOptions = board => {
)(selectedBoardOptions);
};
// =============================================================================
//
// Error wrappers
//
// =============================================================================
// :: Error -> RejectedPromise Error
const wrapCompileError = err =>
Promise.reject(
createError('COMPILE_TOOL_ERROR', {
message: err.message,
code: err.code,
})
);
// :: Error -> RejectedPromise Error
const wrapUploadError = err =>
Promise.reject(
createError('UPLOAD_TOOL_ERROR', {
message: err.message,
code: err.code,
})
);
// =============================================================================
//
// Handlers
@@ -225,6 +253,26 @@ const patchFqbnWithOptions = board => {
export const prepareSketchDir = () =>
fse.mkdtemp(path.resolve(os.tmpdir(), 'xod_temp_sketchbook'));
/**
* Prepare `__packages__` directory inside user's workspace if
* it does not prepared earlier:
* - copy bundled package index json files
* - migrate old arduino packages if they are exist
* - create `extra.txt` file
*
* Returns Path to the `__packages__` directory inside user's workspace
*
* :: Path -> Promise Path Error
*/
const prepareWorkspacePackagesDir = async wsPath => {
const packagesDirPath = getArduinoPackagesPath(wsPath);
await copyPackageIndexes(packagesDirPath);
await migrateArduinoPackages(wsPath);
await ensureExtraTxt(wsPath);
return packagesDirPath;
};
/**
* Creates an instance of ArduinoCli.
*
@@ -237,31 +285,42 @@ export const prepareSketchDir = () =>
*
* :: Path -> Promise ArduinoCli Error
*/
export const create = sketchDir =>
loadWorkspacePath().then(async wsPath => {
const arduinoCliPath = await getArduinoCliPath();
export const create = async sketchDir => {
const wsPath = await loadWorkspacePath();
const arduinoCliPath = await getArduinoCliPath();
const packagesDirPath = await prepareWorkspacePackagesDir(wsPath);
const packagesDirPath = getArduinoPackagesPath(wsPath);
await copyPackageIndexes(packagesDirPath);
await migrateArduinoPackages();
await ensureExtraTxt();
if (!await fse.pathExists(arduinoCliPath)) {
throw createError('ARDUINO_CLI_NOT_FOUND', {
path: arduinoCliPath,
isDev: IS_DEV,
});
}
return arduinoCli(arduinoCliPath, {
arduino_data: packagesDirPath,
sketchbook_path: sketchDir,
board_manager: {
additional_urls: BUNDLED_ADDITIONAL_URLS,
},
if (!await fse.pathExists(arduinoCliPath)) {
throw createError('ARDUINO_CLI_NOT_FOUND', {
path: arduinoCliPath,
isDev: IS_DEV,
});
}
return arduinoCli(arduinoCliPath, {
arduino_data: packagesDirPath,
sketchbook_path: sketchDir,
board_manager: {
additional_urls: BUNDLED_ADDITIONAL_URLS,
},
});
};
/**
* Updates path to the `arduino_data` in the arduino-cli `.cli-config.yml`
* and prepares `__packages__` directory in the user's workspace if needed.
*
* We have to call this function when user changes workspace to make all
* functions provided by this module works properly without restarting the IDE.
*
* :: ArduinoCli -> Path -> Promise Object Error
*/
export const switchWorkspace = async (cli, newWsPath) => {
const oldConfig = await cli.dumpConfig();
const packagesDirPath = await prepareWorkspacePackagesDir(newWsPath);
const newConfig = R.assoc('arduino_data', packagesDirPath, oldConfig);
return cli.updateConfig(newConfig);
};
/**
* Updates package index json files.
@@ -280,7 +339,8 @@ const updateIndexes = async cli => {
R.reject(isAmong(urls)),
R.split('\r\n'),
p => fse.readFile(p, { encoding: 'utf8' }),
ensureExtraTxt
ensureExtraTxt,
loadWorkspacePath
)();
};
@@ -369,18 +429,20 @@ const uploadThroughCloud = async (onProgress, cli, payload) => {
message: CODE_COMPILED,
tab: 'compiler',
});
const uploadLog = await cli.upload(
stdout =>
onProgress({
percentage: 60,
message: stdout,
tab: 'uploader',
}),
payload.port.comName,
payload.board.fqbn,
sketchName,
false
);
const uploadLog = await cli
.upload(
stdout =>
onProgress({
percentage: 60,
message: stdout,
tab: 'uploader',
}),
payload.port.comName,
payload.board.fqbn,
sketchName,
false
)
.catch(wrapUploadError);
onProgress({
percentage: 100,
message: '',
@@ -416,17 +478,19 @@ const uploadThroughUSB = async (onProgress, cli, payload) => {
tab: 'compiler',
});
const compileLog = await cli.compile(
stdout =>
onProgress({
percentage: 40,
message: stdout,
tab: 'compiler',
}),
payload.board.fqbn,
sketchName,
false
);
const compileLog = await cli
.compile(
stdout =>
onProgress({
percentage: 40,
message: stdout,
tab: 'compiler',
}),
payload.board.fqbn,
sketchName,
false
)
.catch(wrapCompileError);
onProgress({
percentage: 50,
@@ -434,18 +498,20 @@ const uploadThroughUSB = async (onProgress, cli, payload) => {
tab: 'uploader',
});
const uploadLog = await cli.upload(
stdout =>
onProgress({
percentage: 60,
message: stdout,
tab: 'uploader',
}),
payload.port.comName,
payload.board.fqbn,
sketchName,
false
);
const uploadLog = await cli
.upload(
stdout =>
onProgress({
percentage: 60,
message: stdout,
tab: 'uploader',
}),
payload.port.comName,
payload.board.fqbn,
sketchName,
false
)
.catch(wrapUploadError);
onProgress({
percentage: 100,
message: '',

View File

@@ -241,13 +241,17 @@ const onReady = () => {
.prepareSketchDir()
.then(aCli.create)
.then(arduinoCli => {
// TODO: Refactor (WS)
aCli.subscribeListBoards(arduinoCli);
aCli.subscribeUpload(arduinoCli);
aCli.subscribeUpdateIndexes(arduinoCli);
aCli.subscibeCheckUpdates(arduinoCli);
aCli.subscribeUpgradeArduinoPackages(arduinoCli);
// On switching workspace -> update arduino-cli config
ipcMain.on(EVENTS.SWITCH_WORKSPACE, (event, newWsPath) =>
aCli.switchWorkspace(arduinoCli, newWsPath)
);
subscribeOnCheckArduinoDependencies(arduinoCli);
subscribeOnInstallArduinoDependencies(arduinoCli);
})

View File

@@ -12,7 +12,6 @@ import * as R from 'ramda';
import * as fse from 'fs-extra';
import { app } from 'electron';
import { loadWorkspacePath } from './workspaceActions';
import { ARDUINO_PACKAGES_DIRNAME } from './constants';
const OLD_PACKAGE_VERSIONS = {
@@ -32,9 +31,9 @@ const moveWithoutEexistError = (from, to) =>
/**
* Moves old packages into Users' workspace.
*
* :: _ -> Promise 0 Error
* :: Path -> Promise 0 Error
*/
export default async () => {
export default async wsPath => {
const appData = app.getPath('userData');
const oldPackagesDir = path.join(appData, 'packages');
if (!await fse.pathExists(oldPackagesDir)) return 0;
@@ -42,7 +41,6 @@ export default async () => {
const oldHardwareDir = path.join(oldPackagesDir, 'arduino', 'hardware');
const archs = await fse.readdir(oldHardwareDir);
const wsPath = await loadWorkspacePath();
const wsPackagesDir = path.join(wsPath, ARDUINO_PACKAGES_DIRNAME, 'packages');
const wsHardwareDir = path.join(wsPackagesDir, 'arduino', 'hardware');

View File

@@ -4,8 +4,14 @@ export default {
note: `Cloud compilation does not support ${boardName} yet.`,
solution: 'Try to compile it on your own computer',
}),
COMPILE_TOOL_ERROR: ({ message }) => ({
title: 'Compilation failed',
note: `Command ${message}`,
solution:
'The generated C++ code contains errors. It can be due to a bad node implementation or if your board is not compatible with XOD runtime code. The original compiler error message is above. Fix C++ errors to continue. If you believe it is a bug, report the problem to XOD developers.',
}),
UPLOAD_TOOL_ERROR: ({ message }) => ({
title: 'Upload tool exited with error',
title: 'Upload failed',
note: `Command ${message}`,
solution:
'Make sure the board is connected, the cable is working, the board model set correctly, the upload port belongs to the board, the board drivers are installed, the upload options (if any) match your board specs.',

View File

@@ -331,15 +331,7 @@ class App extends client.App {
board,
port,
}
).catch(err => {
console.error(err); // eslint-disable-line no-console
return Promise.reject(
createError('UPLOAD_TOOL_ERROR', {
message: err.message,
code: err.code,
})
);
})
)
)
.then(() => proc.success())
.then(() => {

View File

@@ -107,7 +107,7 @@ const rawItems = {
label: 'Upload to Arduino...',
},
updatePackages: {
label: 'Upgrade Arduino Packages & Toolchains...',
label: 'Upgrade Arduino Packages && Toolchains...',
},
view: {

View File

@@ -11,6 +11,8 @@
void setup();
void loop();
#define A0 14
uint32_t millis();
void delay(uint32_t);

View File

@@ -16,7 +16,7 @@ void evaluate(Context ctx) {
State* state = getState(ctx);
auto port = (int)getValue<input_PORT>(ctx);
if (port > NUM_DIGITAL_PINS - 1) {
if (!isValidDigitalPort(port)) {
emitValue<output_ERR>(ctx, 1);
return;
}

View File

@@ -8,8 +8,8 @@ void evaluate(Context ctx) {
return;
const uint8_t port = getValue<input_PORT>(ctx);
bool err = (port < A0 || port > A0 + NUM_ANALOG_INPUTS - 1);
if (err) {
if (!isValidAnalogPort(port)) {
emitValue<output_ERR>(ctx, 1);
return;
}

View File

@@ -8,8 +8,7 @@ void evaluate(Context ctx) {
return;
const uint8_t port = getValue<input_PORT>(ctx);
bool err = (port > NUM_DIGITAL_PINS - 1);
if (err) {
if (!isValidDigitalPort(port)) {
emitValue<output_ERR>(ctx, 1);
return;
}

View File

@@ -8,8 +8,7 @@ void evaluate(Context ctx) {
return;
const uint8_t port = getValue<input_PORT>(ctx);
bool err = (port > NUM_DIGITAL_PINS - 1);
if (err) {
if (!isValidDigitalPort(port)) {
emitValue<output_ERR>(ctx, 1);
return;
}

View File

@@ -14,9 +14,8 @@ void evaluate(Context ctx) {
return;
const uint8_t port = getValue<input_PORT>(ctx);
bool err = (port > NUM_DIGITAL_PINS - 1);
if (err) {
if (!isValidDigitalPort(port)) {
emitValue<output_ERR>(ctx, 1);
return;
}

View File

@@ -777,6 +777,22 @@ bool isTimedOut(const ContextT* ctx) {
return detail::isTimedOut(ctx->_node);
}
bool isValidDigitalPort(uint8_t port) {
#ifdef NUM_DIGITAL_PINS
return port < NUM_DIGITAL_PINS;
#else
return true;
#endif
}
bool isValidAnalogPort(uint8_t port) {
#ifdef NUM_ANALOG_INPUTS
return port >= A0 && port < A0 + NUM_ANALOG_INPUTS;
#else
return port >= A0;
#endif
}
} // namespace xod
//----------------------------------------------------------------------------
@@ -1235,8 +1251,7 @@ void evaluate(Context ctx) {
return;
const uint8_t port = getValue<input_PORT>(ctx);
bool err = (port > NUM_DIGITAL_PINS - 1);
if (err) {
if (!isValidDigitalPort(port)) {
emitValue<output_ERR>(ctx, 1);
return;
}

View File

@@ -777,6 +777,22 @@ bool isTimedOut(const ContextT* ctx) {
return detail::isTimedOut(ctx->_node);
}
bool isValidDigitalPort(uint8_t port) {
#ifdef NUM_DIGITAL_PINS
return port < NUM_DIGITAL_PINS;
#else
return true;
#endif
}
bool isValidAnalogPort(uint8_t port) {
#ifdef NUM_ANALOG_INPUTS
return port >= A0 && port < A0 + NUM_ANALOG_INPUTS;
#else
return port >= A0;
#endif
}
} // namespace xod
//----------------------------------------------------------------------------

View File

@@ -777,6 +777,22 @@ bool isTimedOut(const ContextT* ctx) {
return detail::isTimedOut(ctx->_node);
}
bool isValidDigitalPort(uint8_t port) {
#ifdef NUM_DIGITAL_PINS
return port < NUM_DIGITAL_PINS;
#else
return true;
#endif
}
bool isValidAnalogPort(uint8_t port) {
#ifdef NUM_ANALOG_INPUTS
return port >= A0 && port < A0 + NUM_ANALOG_INPUTS;
#else
return port >= A0;
#endif
}
} // namespace xod
//----------------------------------------------------------------------------

View File

@@ -777,6 +777,22 @@ bool isTimedOut(const ContextT* ctx) {
return detail::isTimedOut(ctx->_node);
}
bool isValidDigitalPort(uint8_t port) {
#ifdef NUM_DIGITAL_PINS
return port < NUM_DIGITAL_PINS;
#else
return true;
#endif
}
bool isValidAnalogPort(uint8_t port) {
#ifdef NUM_ANALOG_INPUTS
return port >= A0 && port < A0 + NUM_ANALOG_INPUTS;
#else
return port >= A0;
#endif
}
} // namespace xod
//----------------------------------------------------------------------------