Fix script execution for Linux VSCode development
This commit improves the VSCode configuration script for Linux-based development environments. It fixes a script execution failure in the deskto version during development when using VSCode installed via Snap or Flatpak. It resolves the following error encountered during script execution in development mode (`npm run electron:dev`): `symbol lookup error: /snap/core20/current/lib/x86_64-linux-gnu/libpthread.so.0: undefined symbol: __libc_pthread_init, version GLIBC_PRIVATE` Changes: - Add a setting in VSCode configuration script to workaround script execution errors in sandboxed VSCode installations on Linux (see see microsoft/vscode#179274). - Migrate the configuration script to Python for cross-platform compatibility and simplicity. - Refactor the script for better extensibility. - Automate installation of recommended VSCode extensions. - Recommend VSCode Pylint extension for Python linting. - Standardize Python development settings in `.editorconfig`.
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
# Top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*.{js,jsx,ts,tsx,vue,sh}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -9,3 +12,8 @@ max_line_length = 100
|
||||
[{Dockerfile}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.py]
|
||||
indent_size = 4 # PEP 8 (the official Python style guide) recommends using 4 spaces per indentation level
|
||||
indent_style = space
|
||||
max_line_length = 100
|
||||
|
||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -16,7 +16,8 @@
|
||||
// Scripting
|
||||
"timonwong.shellcheck", // Lints bash files.
|
||||
"ms-vscode.powershell", // Lints PowerShell files.
|
||||
"ms-python.python", // Lints Python files.
|
||||
"ms-python.python", // Python IntelliSense, debugging, and basic linting.
|
||||
"ms-python.pylint", // Lints Python files
|
||||
// Distribution
|
||||
"ms-azuretools.vscode-docker" // Adds Docker support.
|
||||
]
|
||||
|
||||
@@ -17,6 +17,7 @@ See [ci-cd.md](./ci-cd.md) for more information.
|
||||
- Refer to [action.yml](./../.github/actions/setup-node/action.yml) for the minimum required version compatible with the automated workflows.
|
||||
- 💡 Recommended: Use [`nvm`](https://github.com/nvm-sh/nvm) CLI to install and switch between Node.js versions.
|
||||
- Install dependencies using `npm install` (or [`npm run install-deps`](#utility-scripts) for more options).
|
||||
- For Visual Studio Code users, running the configuration script is recommended to optimize the IDE settings, as detailed in [utility scripts](#utility-scripts).
|
||||
|
||||
### Testing
|
||||
|
||||
@@ -79,8 +80,8 @@ See [ci-cd.md](./ci-cd.md) for more information.
|
||||
- [**`npm run install-deps [-- <options>]`**](../scripts/npm-install.js):
|
||||
- Manages NPM dependency installation, it offers capabilities like doing a fresh install, retries on network errors, and other features.
|
||||
- For example, you can run `npm run install-deps -- --fresh` to do clean installation of dependencies.
|
||||
- [**`./scripts/configure-vscode.sh`**](../scripts/configure-vscode.sh):
|
||||
- This script checks and sets the necessary configurations for VSCode in `settings.json` file.
|
||||
- [**`python ./scripts/configure_vscode.py`**](../scripts/configure_vscode.py):
|
||||
- Optimizes Visual Studio Code settings and installs essential extensions, enhancing the development environment.
|
||||
|
||||
#### Automation scripts
|
||||
|
||||
@@ -96,3 +97,4 @@ See [ci-cd.md](./ci-cd.md) for more information.
|
||||
You should use EditorConfig to follow project style.
|
||||
|
||||
For Visual Studio Code, [`.vscode/extensions.json`](./../.vscode/extensions.json) includes list of recommended extensions.
|
||||
You can use [VSCode configuration script](#utility-scripts) to automatically install those.
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script ensures that the '.vscode/settings.json' file exists and is configured correctly for ESLint validation on Vue and JavaScript files.
|
||||
# See https://web.archive.org/web/20230801024405/https://eslint.vuejs.org/user-guide/#visual-studio-code
|
||||
|
||||
declare -r SETTINGS_FILE='.vscode/settings.json'
|
||||
declare -ra CONFIG_KEYS=('vue' 'javascript' 'typescript')
|
||||
declare -r TEMP_FILE="tmp.$$.json"
|
||||
|
||||
main() {
|
||||
ensure_vscode_directory_exists
|
||||
create_or_update_settings
|
||||
}
|
||||
|
||||
ensure_vscode_directory_exists() {
|
||||
local dir_name
|
||||
dir_name=$(dirname "${SETTINGS_FILE}")
|
||||
if [[ ! -d ${dir_name} ]]; then
|
||||
mkdir -p "${dir_name}"
|
||||
echo "🎉 Created directory: ${dir_name}"
|
||||
fi
|
||||
}
|
||||
|
||||
create_or_update_settings() {
|
||||
if [[ ! -f ${SETTINGS_FILE} ]]; then
|
||||
create_default_settings
|
||||
else
|
||||
add_or_update_eslint_validate
|
||||
fi
|
||||
}
|
||||
|
||||
create_default_settings() {
|
||||
local default_validate
|
||||
default_validate=$(printf '%s' "${CONFIG_KEYS[*]}" | jq -R -s -c -M 'split(" ")')
|
||||
echo "{ \"eslint.validate\": ${default_validate} }" | jq '.' > "${SETTINGS_FILE}"
|
||||
echo "🎉 Created default ${SETTINGS_FILE}"
|
||||
}
|
||||
|
||||
add_or_update_eslint_validate() {
|
||||
if ! jq -e '.["eslint.validate"]' "${SETTINGS_FILE}" >/dev/null; then
|
||||
add_default_eslint_validate
|
||||
else
|
||||
update_eslint_validate
|
||||
fi
|
||||
}
|
||||
|
||||
add_default_eslint_validate() {
|
||||
jq --argjson keys "$(printf '%s' "${CONFIG_KEYS[*]}" \
|
||||
| jq -R -s -c 'split(" ")')" '. += {"eslint.validate": $keys}' "${SETTINGS_FILE}" > "${TEMP_FILE}"
|
||||
replace_and_confirm
|
||||
echo "🎉 Added default 'eslint.validate' to ${SETTINGS_FILE}"
|
||||
}
|
||||
|
||||
update_eslint_validate() {
|
||||
local existing_keys
|
||||
existing_keys=$(jq '.["eslint.validate"]' "${SETTINGS_FILE}")
|
||||
for key in "${CONFIG_KEYS[@]}"; do
|
||||
if ! echo "${existing_keys}" | jq 'index("'"${key}"'")' >/dev/null; then
|
||||
jq '.["eslint.validate"] += ["'"${key}"'"]' "${SETTINGS_FILE}" > "${TEMP_FILE}"
|
||||
mv "${TEMP_FILE}" "${SETTINGS_FILE}"
|
||||
echo "🎉 Updated 'eslint.validate' in ${SETTINGS_FILE} for ${key}"
|
||||
else
|
||||
echo "⏩️ No updated needed for ${key} ${SETTINGS_FILE}."
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
replace_and_confirm() {
|
||||
if mv "${TEMP_FILE}" "${SETTINGS_FILE}"; then
|
||||
echo "🎉 Updated ${SETTINGS_FILE}"
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
||||
162
scripts/configure_vscode.py
Executable file
162
scripts/configure_vscode.py
Executable file
@@ -0,0 +1,162 @@
|
||||
"""
|
||||
This script configures project-level VSCode settings in '.vscode/settings.json' for
|
||||
development and installs recommended extensions from '.vscode/extensions.json'.
|
||||
"""
|
||||
# pylint: disable=missing-function-docstring
|
||||
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import re
|
||||
from typing import Any
|
||||
from shutil import which
|
||||
|
||||
VSCODE_SETTINGS_JSON_FILE: str = '.vscode/settings.json'
|
||||
VSCODE_EXTENSIONS_JSON_FILE: str = '.vscode/extensions.json'
|
||||
|
||||
def main() -> None:
|
||||
ensure_vscode_directory_exists()
|
||||
ensure_setting_file_exists()
|
||||
add_or_update_settings()
|
||||
install_recommended_extensions()
|
||||
|
||||
def ensure_vscode_directory_exists() -> None:
|
||||
vscode_directory_path = os.path.dirname(VSCODE_SETTINGS_JSON_FILE)
|
||||
try:
|
||||
os.makedirs(vscode_directory_path, exist_ok=True)
|
||||
print_success(f"Created or verified directory: {vscode_directory_path}")
|
||||
except OSError as error:
|
||||
print_error(f"Error handling directory {vscode_directory_path}: {error}")
|
||||
|
||||
def ensure_setting_file_exists() -> None:
|
||||
try:
|
||||
if os.path.isfile(VSCODE_SETTINGS_JSON_FILE):
|
||||
print_success(f"VSCode settings file exists: {VSCODE_SETTINGS_JSON_FILE}")
|
||||
return
|
||||
with open(VSCODE_SETTINGS_JSON_FILE, 'w', encoding='utf-8') as file:
|
||||
json.dump({}, file, indent=4)
|
||||
print_success(f"Created empty {VSCODE_SETTINGS_JSON_FILE}")
|
||||
except IOError as error:
|
||||
print_error(f"Error creating file {VSCODE_SETTINGS_JSON_FILE}: {error}")
|
||||
print(f"📄 Created empty {VSCODE_SETTINGS_JSON_FILE}")
|
||||
|
||||
def add_or_update_settings() -> None:
|
||||
configure_setting_key('eslint.validate', ['vue', 'javascript', 'typescript'])
|
||||
# Set ESLint validation for specific file types.
|
||||
# Details: # pylint: disable-next=line-too-long
|
||||
# - https://web.archive.org/web/20230801024405/https://eslint.vuejs.org/user-guide/#visual-studio-code
|
||||
|
||||
configure_setting_key('terminal.integrated.env.linux', {"GTK_PATH": ""})
|
||||
# Unset GTK_PATH on Linux for Electron development in sandboxed environments
|
||||
# like Snap or Flatpak VSCode installations, enabling script execution.
|
||||
# Details: # pylint: disable-next=line-too-long
|
||||
# - https://archive.ph/2024.01.06-003914/https://github.com/microsoft/vscode/issues/179274, https://web.archive.org/web/20240106003915/https://github.com/microsoft/vscode/issues/179274
|
||||
|
||||
def configure_setting_key(configuration_key: str, desired_value: Any) -> None:
|
||||
try:
|
||||
with open(VSCODE_SETTINGS_JSON_FILE, 'r+', encoding='utf-8') as file:
|
||||
settings: dict = json.load(file)
|
||||
if configuration_key in settings:
|
||||
actual_value = settings[configuration_key]
|
||||
if actual_value == desired_value:
|
||||
print_skip(f"Already configured as desired: \"{configuration_key}\"")
|
||||
return
|
||||
settings[configuration_key] = desired_value
|
||||
file.seek(0)
|
||||
json.dump(settings, file, indent=4)
|
||||
file.truncate()
|
||||
print_success(f"Added or updated configuration: {configuration_key}")
|
||||
except json.JSONDecodeError:
|
||||
print_error(f"Failed to update JSON for key {configuration_key}.")
|
||||
|
||||
def install_recommended_extensions() -> None:
|
||||
if not os.path.isfile(VSCODE_EXTENSIONS_JSON_FILE):
|
||||
print_error(
|
||||
f"The extensions.json file does not exist in the path: {VSCODE_EXTENSIONS_JSON_FILE}."
|
||||
)
|
||||
return
|
||||
with open(VSCODE_EXTENSIONS_JSON_FILE, 'r', encoding='utf-8') as file:
|
||||
json_content: str = remove_json_comments(file.read())
|
||||
try:
|
||||
data: dict = json.loads(json_content)
|
||||
extensions: list[str] = data.get("recommendations", [])
|
||||
if not extensions:
|
||||
print_skip(f"No recommendations found in the {VSCODE_EXTENSIONS_JSON_FILE} file.")
|
||||
return
|
||||
vscode_cli_path = which('code') # More reliable than using `code`, especially on Windows.
|
||||
if vscode_cli_path is None:
|
||||
print_error('Visual Studio Code CLI (`code`) tool not found.')
|
||||
return
|
||||
install_vscode_extensions(vscode_cli_path, extensions)
|
||||
except json.JSONDecodeError:
|
||||
print_error(f"Invalid JSON in {VSCODE_EXTENSIONS_JSON_FILE}")
|
||||
|
||||
def remove_json_comments(json_like: str) -> str:
|
||||
pattern: str = r'(?:"(?:\\.|[^"\\])*"|/\*[\s\S]*?\*/|//.*)|([^:]//.*$)'
|
||||
return re.sub(
|
||||
pattern,
|
||||
lambda m: '' if m.group(1) else m.group(0), json_like, flags=re.MULTILINE,
|
||||
)
|
||||
|
||||
def install_vscode_extensions(vscode_cli_path: str, extensions: list[str]) -> None:
|
||||
successful_installations = 0
|
||||
for ext in extensions:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[vscode_cli_path, "--install-extension", ext],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if "already installed" in result.stdout:
|
||||
print_skip(f"Created or verified directory: {ext}")
|
||||
else:
|
||||
print_success(f"Installed extension: {ext}")
|
||||
successful_installations += 1
|
||||
print_subprocess_output(result)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print_subprocess_output(e)
|
||||
print_error(f"Failed to install extension: {ext}")
|
||||
except FileNotFoundError:
|
||||
print_error(' '.join([
|
||||
f"Visual Studio Code CLI tool not found: {vscode_cli_path}."
|
||||
f"Could not install extension: {ext}",
|
||||
]))
|
||||
total_extensions = len(extensions)
|
||||
print_installation_results(successful_installations, total_extensions)
|
||||
|
||||
def print_subprocess_output(result: subprocess.CompletedProcess[str]) -> None:
|
||||
output = '\n'.join([text.strip() for text in [result.stdout, result.stderr] if text])
|
||||
if not output:
|
||||
return
|
||||
formatted_output = '\t' + output.strip().replace('\n', '\n\t')
|
||||
print(formatted_output)
|
||||
|
||||
def print_installation_results(successful_installations: int, total_extensions: int) -> None:
|
||||
if successful_installations == total_extensions:
|
||||
print_success(
|
||||
f"Successfully installed or verified all {total_extensions} recommended extensions."
|
||||
)
|
||||
elif successful_installations > 0:
|
||||
print_warning(
|
||||
f"Partially successful: Installed or verified {successful_installations} "
|
||||
f"out of {total_extensions} recommended extensions."
|
||||
)
|
||||
else:
|
||||
print_error("Failed to install any of the recommended extensions.")
|
||||
|
||||
def print_error(message: str) -> None:
|
||||
print(f"💀 Error: {message}", file=sys.stderr)
|
||||
|
||||
def print_success(message: str) -> None:
|
||||
print(f"✅ Success: {message}")
|
||||
|
||||
def print_skip(message: str) -> None:
|
||||
print(f"⏩ Skipped: {message}")
|
||||
|
||||
def print_warning(message: str) -> None:
|
||||
print(f"⚠️ Warning: {message}", file=sys.stderr)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user