config_tools: update configurator document and build script

1. update configurator document.
2. fix build script doesn't generate allchecks.xsd issue.
3. add open_devtools command, now we can get detail debug info in prod.
4. remove vconsole component, which will cause some strange js runtime issue in Linux.

Tracked-On: #6691
Signed-off-by: Weiyi Feng <weiyix.feng@intel.com>
This commit is contained in:
Weiyi Feng 2022-04-27 00:03:27 +08:00 committed by acrnsi-robot
parent c9e982557b
commit f9cb5edc7e
13 changed files with 420 additions and 419 deletions

View File

@ -19,20 +19,37 @@ to install system dependencies **(including yarn)**.
#### Linux
In Linux,
In Linux, make sure your already install `git`, `python3`(version>=3.6) and `python3-venv` library,
```bash
$ sudo apt install git python3 python3-venv
# check python3 version
$ python3 --version
Python 3.8.10
```
#### Windows
In Windows, [chocolatey](https://chocolatey.org/) is a Windows package manager,
[Chocolatey](https://chocolatey.org/) is a package manager tool for windows,
you can use `choco install xsltproc` to install `xsltproc` package,
which provide `xmllint` command.
Make sure your system have python which version>3.6,
you can check it by following command line:
```bash
$ python --version
Python 3.8.10
```
If your system doesn't have git and python, you can install it by
`choco install git python3`.
### 2. Clone Project And Install Project Dependencies.
#### Linux
```bash
sudo apt install git
git clone https://github.com/projectacrn/acrn-hypervisor
cd acrn-hypervisor/misc/config_tools
python3 -m pip install -r requirements.txt
@ -43,10 +60,8 @@ yarn
#### Windows
Similar to Linux.
In the Windows environment maybe you need to install git and python3 via chocolatey or manually,
and replace the command line `python3` with `py -3`.
Similar to Linux, in the Windows environment,
you need replace the command line `python3` with `python`.
### 3. How To Build
@ -65,7 +80,7 @@ Run following command in the 'acrn-hypervisor' directory.
```shell
cd misc/config_tools
python scenario_config/schema_slicer.py
python scenario_config/xs2js.py
python scenario_config/jsnoschema/convert.py
xmllint --xinclude schema/datachecks.xsd > schema/allchecks.xsd
python -m build
@ -88,6 +103,6 @@ acrn-configurator
#### Windows
You can find msi(Windows)/dmg(macOS) folder under the
`misc/config_tools/configurator/src-tauri/target/release/bundle`
You can find installer under the
`misc/config_tools/configurator/src-tauri/target/release/bundle/msi`
directory, the installer in the folder.

View File

@ -9,10 +9,13 @@
<!-- must using style, otherwise style will lost -->
<body style="margin: 0;padding: 0;height: calc(100vh - 1px);width: calc(100vw - 1px);border: 1px #787878 solid;">
<div id="app">
<div data-tauri-drag-region="true" style="width: 100%;height: 100%;position:fixed;overflow:hidden;display: flex;justify-content: center;align-items: center;background: #007B81">
<div data-tauri-drag-region="true"
style="width: 100%;height: 100%;position:fixed;overflow:hidden;display: flex;justify-content: center;align-items: center;background: #007B81">
<div style="display: block;color: white;text-align: center;user-select: none">
<text style="font-size:36px;display: flex;padding: 16px">
<img data-tauri-drag-region="true" src="src/assets/images/ACRN_Logo.svg" alt="logo" style="height: 49px;padding-right: 1.5rem"/>
<img src="/src/assets/images/ACRN_Logo.svg"
alt="logo" onClick="openDevTools()"
style="height: 49px;padding-right: 1.5rem;cursor: pointer"/>
Loading...
</text>
<text style="font-size:24px;display: block">Please wait for 5 seconds~</text>

View File

@ -19,7 +19,6 @@
"@popperjs/core": "^2.11.5",
"@rollup/plugin-replace": "^4.0.0",
"@tauri-apps/api": "^1.0.0-rc.3",
"@vicons/carbon": "^0.12.0",
"@vicons/fa": "^0.12.0",
"@vicons/utils": "^0.1.4",
"ajv-i18n": "^4.2.0",
@ -30,7 +29,6 @@
"naive-ui": "^2.28.1",
"node-fetch": "2",
"sass": "^1.50.0",
"vconsole": "^3.14.6",
"vfonts": "^0.0.3",
"vue": "^3.2.25",
"vue-router": "4"

View File

@ -1,3 +1,3 @@
fn main() {
tauri_build::build()
tauri_build::build()
}

View File

@ -1,6 +1,6 @@
max_width = 100
hard_tabs = false
tab_spaces = 2
tab_spaces = 4
newline_style = "Auto"
use_small_heuristics = "Default"
reorder_imports = true

View File

@ -2,16 +2,15 @@ use std::borrow::Borrow;
use std::ops::Add;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use glob::{glob_with, MatchOptions};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use tauri::Window;
use std::fs::{self, File};
use std::io;
use std::io::prelude::*;
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
#[repr(u16)]
#[non_exhaustive]
@ -40,17 +39,19 @@ pub struct Configurator {
pub config_data: ConfigData,
pub working_folder: String,
}
pub fn write_file(path: PathBuf, content: String) -> Result<(), String> {
fs::write(path, content).map_err(|e| e.to_string())
}
impl ConfigData {
fn new() -> ConfigData {
let history = History { working_folder: vec![], board_file: vec![], scenario_file: vec![] };
let history = History {
working_folder: vec![],
board_file: vec![],
scenario_file: vec![],
};
ConfigData { history }
}
@ -65,12 +66,11 @@ impl ConfigData {
fn deserialize(config_json: String) -> Result<ConfigData, String> {
match serde_json::from_str(&config_json.to_string()) {
Ok(config_data) => Ok(config_data),
Err(e) => Err(e.to_string())
Err(e) => Err(e.to_string()),
}
}
}
impl Configurator {
pub fn new() -> Self {
match Self::ensure_config_file() {
@ -101,13 +101,12 @@ impl Configurator {
None => {
return Err("get config_dir and home_dir error!".to_string());
}
Some(path) => path
Some(path) => path,
}
}
Some(path) => path
Some(path) => path,
};
// get acrn-configurator dir path and check it exist
let config_dir = config_base.join(".acrn-configurator");
log::info!("current config_dir is {}.", config_dir.to_str().unwrap());
@ -122,14 +121,13 @@ impl Configurator {
}
}
// get config.json file path and check it exist
let default_config_path = config_dir.join("config.json");
if !default_config_path.is_file() {
let empty_config = ConfigData::new();
match fs::write(&default_config_path, empty_config.serialize()) {
Ok(_) => {}
Err(e) => return Err(e.to_string())
Err(e) => return Err(e.to_string()),
};
}
Ok(default_config_path)
@ -137,7 +135,7 @@ impl Configurator {
pub fn init(config_file_path: PathBuf) -> Configurator {
let config_json = match fs::read_to_string(&config_file_path) {
Ok(data) => { data }
Ok(data) => data,
Err(e) => {
log::warn!("read config error! error: {}", e.to_string());
log::warn!("Use default blank config to start due to read config failed.");
@ -170,7 +168,6 @@ impl Configurator {
}
}
pub fn save_config(&self) {
if !self.config_write_enable {
return;
@ -178,7 +175,7 @@ impl Configurator {
match fs::write(&self.config_path, self.config_data.serialize()) {
Ok(_) => {}
Err(e) => {
log::warn!("Write config error! error:{}",e.to_string())
log::warn!("Write config error! error:{}", e.to_string())
}
}
}
@ -187,31 +184,52 @@ impl Configurator {
let path_string: String = history_path.to_string_lossy().parse().unwrap();
match history_type {
HistoryType::WorkingFolder => {
self.config_data.history.working_folder.insert(0, path_string);
self.config_data.history.working_folder = self.config_data.history.working_folder.clone().into_iter().unique().collect()
self.config_data
.history
.working_folder
.insert(0, path_string);
self.config_data.history.working_folder = self
.config_data
.history
.working_folder
.clone()
.into_iter()
.unique()
.collect()
}
HistoryType::Board => {
self.config_data.history.board_file.insert(0, path_string);
self.config_data.history.board_file = self.config_data.history.board_file.clone().into_iter().unique().collect()
self.config_data.history.board_file = self
.config_data
.history
.board_file
.clone()
.into_iter()
.unique()
.collect()
}
HistoryType::Scenario => {
self.config_data.history.scenario_file.insert(0, path_string);
self.config_data.history.scenario_file = self.config_data.history.scenario_file.clone().into_iter().unique().collect()
self.config_data
.history
.scenario_file
.insert(0, path_string);
self.config_data.history.scenario_file = self
.config_data
.history
.scenario_file
.clone()
.into_iter()
.unique()
.collect()
}
};
}
pub fn get_history(&self, history_type: HistoryType) -> &[String] {
match history_type {
HistoryType::WorkingFolder => {
self.config_data.history.working_folder.borrow()
}
HistoryType::Board => {
self.config_data.history.board_file.borrow()
}
HistoryType::Scenario => {
self.config_data.history.scenario_file.borrow()
}
HistoryType::WorkingFolder => self.config_data.history.working_folder.borrow(),
HistoryType::Board => self.config_data.history.board_file.borrow(),
HistoryType::Scenario => self.config_data.history.scenario_file.borrow(),
}
}
@ -231,24 +249,23 @@ impl Configurator {
};
let pattern = self.working_folder.clone().add("/.*\\.board\\.xml");
let files = match glob_with(&pattern, options).map_err(|e| e.to_string()) {
Ok(files) => { files }
Err(e) => return Err(e.to_string())
Ok(files) => files,
Err(e) => return Err(e.to_string()),
};
for entry in files {
match entry {
Ok(filepath) => {
match fs::remove_file(&filepath) {
Ok(_) => {}
Err(e) => {
let err_msg = format!(
"Can not delete file:{} error: {}",
filepath.to_str().unwrap_or_else(|| "").to_string(), e.to_string()
);
log::warn!("{}",err_msg);
return Err(err_msg);
}
Ok(filepath) => match fs::remove_file(&filepath) {
Ok(_) => {}
Err(e) => {
let err_msg = format!(
"Can not delete file:{} error: {}",
filepath.to_str().unwrap_or_else(|| "").to_string(),
e.to_string()
);
log::warn!("{}", err_msg);
return Err(err_msg);
}
}
},
Err(e) => {
log::error!("find old board error! error:{}", e.to_string())
}
@ -266,7 +283,8 @@ static mut WORKING_FOLDER: String = String::new();
#[tauri::command]
pub fn get_history(history_type: HistoryType) -> Result<String, ()> {
let configurator = Configurator::new();
let history = serde_json::to_string(configurator.get_history(history_type)).unwrap_or_else(|_| String::from("[]"));
let history = serde_json::to_string(configurator.get_history(history_type))
.unwrap_or_else(|_| String::from("[]"));
Ok(history)
}
@ -290,7 +308,6 @@ pub fn set_working_folder(working_folder: String) -> Result<(), ()> {
Ok(())
}
#[tauri::command]
pub fn write_board(board_name: String, contents: String) -> Result<(), String> {
let mut configurator = Configurator::new();
@ -300,7 +317,6 @@ pub fn write_board(board_name: String, contents: String) -> Result<(), String> {
configurator.write_board(board_name, contents)
}
#[tauri::command]
pub fn force_reset() -> Result<(), ()> {
let mut configurator = Configurator::new();
@ -311,12 +327,8 @@ pub fn force_reset() -> Result<(), ()> {
#[tauri::command]
pub fn get_home() -> Result<String, ()> {
match dirs::home_dir() {
None => {
Ok(String::new())
}
Some(path) => {
Ok(path.to_str().unwrap().to_string())
}
None => Ok(String::new()),
Some(path) => Ok(path.to_str().unwrap().to_string()),
}
}
@ -330,8 +342,7 @@ pub struct DirEntry {
pub fn acrn_read(file_path: &str) -> Result<String, String> {
let mut file = File::open(file_path).map_err(|e| e.to_string())?;
let mut contents = String::new();
file
.read_to_string(&mut contents)
file.read_to_string(&mut contents)
.map_err(|e| e.to_string())?;
Ok(contents)
}
@ -339,8 +350,7 @@ pub fn acrn_read(file_path: &str) -> Result<String, String> {
#[tauri::command]
pub fn acrn_write(file_path: &str, contents: &str) -> Result<(), String> {
let mut file = File::create(file_path).map_err(|e| e.to_string())?;
file
.write_all(contents.as_bytes())
file.write_all(contents.as_bytes())
.map_err(|e| e.to_string())?;
Ok(())
}
@ -353,14 +363,15 @@ pub fn acrn_is_file(path: &str) -> bool {
}
#[tauri::command]
pub fn acrn_create_dir(path: &str) -> Result<(), String> {
fs::create_dir(path).map_err(|e| e.to_string())
pub fn acrn_create_dir(path: &str, recursive: bool) -> Result<(), String> {
if recursive {
fs::create_dir_all(path).map_err(|e| e.to_string())
} else {
fs::create_dir(path).map_err(|e| e.to_string())
}
}
fn read_dir<P: AsRef<Path>>(
path: P,
recursive: bool,
) -> io::Result<Vec<DirEntry>> {
fn read_dir<P: AsRef<Path>>(path: P, recursive: bool) -> io::Result<Vec<DirEntry>> {
let path = path.as_ref();
let mut entries = Vec::new();
for entry in fs::read_dir(path)? {
@ -377,10 +388,11 @@ fn read_dir<P: AsRef<Path>>(
}
#[tauri::command]
pub fn acrn_read_dir(
path: &str,
recursive: bool,
) -> Result<Vec<DirEntry>, String> {
pub fn acrn_read_dir(path: &str, recursive: bool) -> Result<Vec<DirEntry>, String> {
read_dir(path, recursive).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn open_devtools(window: Window) {
window.open_devtools()
}

View File

@ -1,11 +1,10 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
mod configurator;
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
@ -20,6 +19,7 @@ fn main() {
configurator::acrn_is_file,
configurator::acrn_read_dir,
configurator::acrn_create_dir,
configurator::open_devtools
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

View File

@ -1,5 +1,5 @@
<template>
<nav class="navbar navbar-expand navbar-dark bg-navbar">
<nav class="navbar navbar-expand navbar-dark bg-navbar" data-tauri-drag-region="true">
<div data-tauri-drag-region="true" class="container-fluid">
<span class="navbar-brand">
<img alt="ACRN"
@ -10,6 +10,9 @@
<text class="d-inline align-bottom logo-text" data-tauri-drag-region="true">Configurator</text>
</span>
<div class="controlButtons d-flex justify-content-between align-items-center" data-tauri-drag-region="true">
<Icon @click="openDevtools" size="20px" color="white">
<Cog/>
</Icon>
<Icon @click="minus" size="20px" color="white">
<Minus/>
</Icon>
@ -27,13 +30,14 @@
<script>
import {Icon} from '@vicons/utils'
import {Minus, WindowMaximizeRegular, Times} from '@vicons/fa'
import {Minus, WindowMaximizeRegular, Times, Cog} from '@vicons/fa'
import {appWindow} from "@tauri-apps/api/window";
import {invoke} from "@tauri-apps/api";
export default {
name: "ControlBar",
components: {Icon, Minus, WindowMaximizeRegular, Times},
components: {Icon, Minus, WindowMaximizeRegular, Times, Cog},
methods: {
minus() {
appWindow.minimize()
@ -49,6 +53,9 @@ export default {
},
close: () => {
appWindow.close()
},
openDevtools: () => {
invoke('open_devtools', {});
}
}
}
@ -83,7 +90,7 @@ export default {
.controlButtons {
margin-right: 30px;
width: 130px;
width: 9rem;
filter: drop-shadow(3px 3px 2px rgb(0 0 0 / 0.4));
}

View File

@ -77,8 +77,8 @@ class Configurator {
return invoke('acrn_read_dir', {path, recursive})
}
creatDir(path: String) {
return invoke('acrn_create_dir', {path})
creatDir(path: String, recursive = true) {
return invoke('acrn_create_dir', {path, recursive})
}
runPython(code: String, isJSON = false): String | Object {

View File

@ -1,12 +1,19 @@
const isTauri = !!window.__TAURI_IPC__;
const isRelease = location.host === 'tauri.localhost';
import VConsole from 'vconsole';
if (isRelease) {
const vConsole = new VConsole();
if (isTauri) {
let openCount = 0
function openDevTools() {
openCount++;
console.log(`openCount ${openCount} of 5`)
if (openCount >= 5) {
invoke('open_devtools', {})
}
}
window.openDevTools = openDevTools;
}
import {createApp} from 'vue'
import App from './App.vue'
import {loadPyodide} from "/thirdLib/pyodide/pyodide";

View File

@ -7,13 +7,6 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef"
integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==
"@babel/runtime@^7.17.2":
version "7.17.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72"
integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==
dependencies:
regenerator-runtime "^0.13.4"
"@css-render/plugin-bem@^0.15.9":
version "0.15.9"
resolved "https://registry.yarnpkg.com/@css-render/plugin-bem/-/plugin-bem-0.15.9.tgz#204ccdb65fdfd293b8cbcf587c8f31633f41f513"
@ -175,11 +168,6 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.25.tgz#527051f3c2f77aa52e5dc74e45a3da5fb2301448"
integrity sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==
"@vicons/carbon@^0.12.0":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@vicons/carbon/-/carbon-0.12.0.tgz#dfcc5d6283662eccee55700b2d5c29e688a70f5a"
integrity sha512-kCOgr/ZOhZzoiFLJ8pwxMa2TMxrkCUOA22qExPabus35F4+USqzcsxaPoYtqRd9ROOYiHrSqwapak/ywF0D9bg==
"@vicons/fa@^0.12.0":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@vicons/fa/-/fa-0.12.0.tgz#a5f92db45990f8a47b5eeedcdb9b673f0dababc8"
@ -403,21 +391,11 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
copy-text-to-clipboard@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c"
integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==
core-js@3.x.x:
version "3.22.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.1.tgz#1936e4f1da82675fe22ae10ee60ef638cd9752fd"
integrity sha512-l6CwCLq7XgITOQGhv1dIUmwCFoqFjyQ6zQHUCQlS0xKmb9d6OHIg8jDiEoswhaettT21BSF5qKr6kbvE+aKwxw==
core-js@^3.11.0:
version "3.22.2"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.2.tgz#3ea0a245b0895fa39d1faa15fe75d91ade504a01"
integrity sha512-Z5I2vzDnEIqO2YhELVMFcL1An2CIsFe9Q7byZhs8c/QxummxZlAHw33TUHbIte987LkisOgL0LwQ1P9D6VISnA==
css-render@^0.13.2:
version "0.13.9"
resolved "https://registry.yarnpkg.com/css-render/-/css-render-0.13.9.tgz#56f28c180a83a10ec610a12a9ad9fec087ac9dc1"
@ -741,11 +719,6 @@ magic-string@^0.25.7:
dependencies:
sourcemap-codec "^1.4.8"
mutation-observer@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/mutation-observer/-/mutation-observer-1.0.3.tgz#42e9222b101bca82e5ba9d5a7acf4a14c0f263d0"
integrity sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA==
naive-ui@^2.28.1:
version "2.28.1"
resolved "https://registry.yarnpkg.com/naive-ui/-/naive-ui-2.28.1.tgz#73739f6bedfaf96a1cb47ed3ec87f5c96554c458"
@ -837,11 +810,6 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
regenerator-runtime@^0.13.4:
version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
@ -935,16 +903,6 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
vconsole@^3.14.6:
version "3.14.6"
resolved "https://registry.yarnpkg.com/vconsole/-/vconsole-3.14.6.tgz#74cbbf9c14e66d26325958cee8a9e8c4086a5115"
integrity sha512-8Ffk2SfNe6EzKqZ0aNnNjpAVBVT7zgJo81lYEJdKySYLVYBeSawdSkWi9fSjDg3WsQhgS1vNPmRqJDTuwdVbnQ==
dependencies:
"@babel/runtime" "^7.17.2"
copy-text-to-clipboard "^3.0.1"
core-js "^3.11.0"
mutation-observer "^1.0.3"
vdirs@^0.1.4, vdirs@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/vdirs/-/vdirs-0.1.8.tgz#a103bc43baca738f8dea912a7e9737154a19dbc2"

View File

@ -206,6 +206,7 @@ def create_configurator_deb(build_dir):
# build command, if you update this, please update misc/config_tools/configurator/README.md#L55
add_cmd_list(cmd_list, 'python3 schema_slicer.py', scenario_config_path)
add_cmd_list(cmd_list, 'python3 converter.py', scenario_config_path / "jsonschema")
add_cmd_list(cmd_list, 'bash -c "xmllint --xinclude schema/datachecks.xsd > schema/allchecks.xsd"', config_tools_path)
add_cmd_list(cmd_list, 'python3 -m build', config_tools_path)
add_cmd_list(cmd_list, 'python3 thirdLib/manager.py install', configurator_path)
add_cmd_list(cmd_list, 'yarn', configurator_path)