diff --git a/.devcontainer/Welcome.md b/.devcontainer/Welcome.md new file mode 100644 index 00000000000..3962686d725 --- /dev/null +++ b/.devcontainer/Welcome.md @@ -0,0 +1,44 @@ +# šŸ‘‹ Welcome to the MNE-Python Dev Container! + +It's so great to see you! 🤩 + +This appears to be the first time you're starting up the container, +or you've restarted it after uninstalling MNE-Python. + +In any case, **we're currently running the MNE-Python installation +procedure.** You can view progress by opening the terminal window +with the the spinning icon (or exclamation mark, in some cases!) in the bottom-right of +your screen! + +Once installation is finished, that terminal window will close and your browser will +open to connect you to a VNC desktop. This is where interactive plots will appear. + +Enjoy, have a great day, and: **Happy hacking!** šŸš€šŸš€šŸš€ + +### Some technical background + +The Dev Container is based on Debian 12 ("bookworm") GNU/Linux. + +Python is installed in a `conda` environment (named `base`), together with a few +dependencies that are currently not available from PyPI for all platforms: + +- `h5py` +- `psutil` +- `pyside6` +- `vtk` + +Everything else is pulled and installed from PyPI through `uv`. Specifically, the command +that is run to install MNE-Python is: + +```shell +pipx run uv pip install -e ".[full-pyside6,dev,test_extra] +``` +It is totally acceptable and safe to install or update dependencies via `pip` if you +wish. + +All `git` pre-commit hooks are automatically installed. + +The noVNC server (for connecting to the VNC desktop via a browser) is exposed on TCP +port 6080. + +The host's `mne_data` directory is mounted at `~/mne_data` inside the container. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000..948c5c83c05 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,162 @@ +{ + "name": "mne-dev", + // More info on the Debian base image we're using here: + // https://github.com/devcontainers/images/tree/main/src/base-debian + // All features listed in the repository's devcontainer configuration are actually + // built into the image. + "image": "mcr.microsoft.com/devcontainers/base:debian-12", + "containerEnv": { + "PYTHONNOUSERSITE": "true", // Make Python ignore the user's site-packages folder if it exists + "XDG_RUNTIME_DIR": "/home/mne-user/.cache/xdgr", // For VNC + "RUNNING_IN_DEV_CONTAINER": "true" + }, + "features": { + // See https://containers.dev/features + // User & shell setup + "ghcr.io/devcontainers/features/common-utils:2": { + "username": "mne-user", + "configureZshAsDefaultShell": true + }, + // Desktop and VNC access + "ghcr.io/devcontainers/features/desktop-lite:1": { + "password": "noPassword" + }, + // Git + "ghcr.io/devcontainers/features/git:1": { + "version": "os-provided", + "ppa": "true" + }, + // APT packages + // We need those for 3D rendering and building the docs. + "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { + "packages": "mesa-utils,libegl1,^libxcb.*-dev,libx11-xcb-dev,libglu1-mesa-dev,libxrender-dev,libxi-dev,libxkbcommon-dev,libxkbcommon-x11-dev,optipng,graphviz" + }, + // Conda + "ghcr.io/mamba-org/devcontainer-features/micromamba:1": { + "channels": "conda-forge", + "packages": "conda python h5py vtk pyside6 pipx" + }, + // Zsh plugins + "ghcr.io/devcontainers-contrib/features/zsh-plugins:0": { + "plugins": "git pip zsh-autosuggestions zsh-syntax-highlighting history-substring-search", + "omzPlugins": "https://github.com/zsh-users/zsh-autosuggestions https://github.com/zsh-users/zsh-syntax-highlighting" + } + }, + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + "settings": { + // Editor settings + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "modificationsIfAvailable", + "editor.renderWhitespace": "trailing", + "editor.rulers": [88], + // General Python settings + "python.defaultInterpreterPath": "/opt/conda/bin/python", + "python.analysis.typeCheckingMode": "basic", + "python.testing.pytestEnabled": true, + "python.testing.pytestArgs": ["--color=yes"], + "ruff.nativeServer": false, + "ruff.importStrategy": "fromEnvironment", + "debugpy.debugJustMyCode": false, + // Python modules and scripts + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + } + }, + // Jupyter notebooks + "notebook.formatOnSave.enabled": true, + "notebook.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, + // JavaScript + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + // Git + "git.allowNoVerifyCommit": true, // Allow omitting pre-commit hooks + "git.allowForcePush": true, + // Screencast settings + "screencastMode.keyboardOverlayTimeout": 5000, + "screencastMode.mouseIndicatorSize": 50, + // Disable telemetry and experiments + "telemetry.telemetryLevel": "off", + "gitlens.telemetry.enabled": false, + "redhat.telemetry.enabled": false, + "workbench.enableExperiments": false, // Should not be necessary if telemetry is off, but let's make it explicit. + // Avoid accumulation of unused forwarded ports. + "remote.restoreForwardedPorts": false, + // Always open Markdown files in the preview (i.e., rendered); double-clicking on a line openes the editor. + "workbench.editorAssociations": { + "*.md": "vscode.markdown.preview.editor" + } + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + // Python Development + "ms-python.python", + "ms-toolsai.jupyter", + "charliermarsh.ruff", + "samuelcolvin.jinjahtml", + // MNE-Python snippets + "hoechenberger.mne-python-extension", + // JavaScript & TypeScript Development + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + // Git + "GitHub.vscode-pull-request-github", + "eamodio.gitlens", + // TOML + "tamasfe.even-better-toml", + // YAML + "redhat.vscode-yaml", + // Spell checking + "streetsidesoftware.code-spell-checker", + // Path / filename autocomplete + "ionutvmi.path-autocomplete" + ] + } + }, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [6080], + "portsAttributes": { + "6080": { + "label": "Web VNC" + } + }, + // Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "mne-user", + "initializeCommand": { + // These commands are run on the host. + // + // We will mount the mne_data directory from the host inside the container to avoid having + // to dowload testing and sample data on container (re)creation. + // Note: On Windows, the .ps1 suffix will be appended automatically to the script name. + "create-mne-data-dir": ".devcontainer/scripts/create-data-dir" + }, + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": { + // Initialize shells to use conda and mamba + "init-conda-shell": "conda init --quiet zsh bash", + // Force pip to always install "globally": This will work in a virtual environment, but fail with the system Python, which is + // precisely what we want: prevent users from accidentally cluttering their user site-packages folder + "disable-pip-user-installs": "pip config set install.system true", + // Create XDG_RUNTIME_DIR + "create-xdg-runtime-dir": "mkdir -p -m 0700 $XDG_RUNTIME_DIR" + }, + "postStartCommand": { + // These commands are run inside the container. + // + // Do all git-config-related things here, otherwise we may run into problems; see: + // https://github.com/microsoft/vscode-remote-release/issues/6810 + "configure-git": "${containerWorkspaceFolder}/.devcontainer/scripts/configure-git.sh" + }, + "mounts": [ + "source=${localEnv:HOME}${localEnv:USERPROFILE}/mne_data,target=/home/mne-user/mne_data,type=bind" + ] +} diff --git a/.devcontainer/scripts/configure-git.sh b/.devcontainer/scripts/configure-git.sh new file mode 100755 index 00000000000..12eb2addcb4 --- /dev/null +++ b/.devcontainer/scripts/configure-git.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -ex + +# Set git default branch name +git config --global init.defaultBranch main + +# Work around "dubious ownership in repository" error +git config --global --add safe.directory /workspaces/* + +# Use VS Code as default git editor, diff, and merge tool +git config --global core.editor 'code --wait --reuse-window' +git config --global --replace-all difftool.default-difftool.cmd 'code --wait --diff $LOCAL $REMOTE' +git config --global --replace-all diff.tool default-difftool +git config --global --replace-all mergetool.code.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED' +git config --global --replace-all merge.tool code + +# Show indicator for "dirty" git repositories in the shell prompt. +# This can be slow on large repositories and should be disabled in that case. +git config --global --replace-all devcontainers-theme.show-dirty 1 + +# Make "git blame" ignore certain commits +git config --local blame.ignoreRevsFile .git-blame-ignore-revs diff --git a/.devcontainer/scripts/create-data-dir b/.devcontainer/scripts/create-data-dir new file mode 100755 index 00000000000..7757e3933e4 --- /dev/null +++ b/.devcontainer/scripts/create-data-dir @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -eu + +# Path to the directory you want to create +DIR=~/mne_data + +# Check if the directory already exists +if [ -d "$DIR" ]; then + echo "Found existing MNE data directory: $DIR" +else + # Create the directory since it does not exist + mkdir -p "$DIR" + # Check if the directory creation was successful + if [ $? -eq 0 ]; then + echo "MNE data directory created: $DIR" + else + echo "Failed to create MNE data directory: $DIR" + exit 1 + fi +fi diff --git a/.devcontainer/scripts/create-data-dir.ps1 b/.devcontainer/scripts/create-data-dir.ps1 new file mode 100644 index 00000000000..5c036ee5979 --- /dev/null +++ b/.devcontainer/scripts/create-data-dir.ps1 @@ -0,0 +1,25 @@ +# Get the path to the user's USERPROFILE folder +$userProfilePath = [System.Environment]::GetFolderPath("UserProfile") + +# Define the path to the new directory +$newDirectoryPath = "$userProfilePath\mne_data" + +# Check if the directory already exists +if (Test-Path -Path $newDirectoryPath) { + Write-Output "Found existing MNE data directory: $newDirectoryPath" +} else { + # Create the directory since it does not exist + try { + New-Item -ItemType Directory -Path $newDirectoryPath -Force + # Verify if the directory was created successfully + if (Test-Path -Path $newDirectoryPath) { + Write-Output "MNE data directory created: $newDirectoryPath" + } else { + Write-Output "Failed to create directory: $newDirectoryPath" + exit 1 + } + } catch { + Write-Output "Error creating MNE data directory: $_" + exit 1 + } +} diff --git a/.devcontainer/scripts/install.sh b/.devcontainer/scripts/install.sh new file mode 100755 index 00000000000..975d0d2963a --- /dev/null +++ b/.devcontainer/scripts/install.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -eu + +# Exit immediately if not running inside a Dev Container +if [ -z "${RUNNING_IN_DEV_CONTAINER+x}" ]; then + echo -e "šŸ‘‹ Not running in dev container, not installing MNE-Python (dev).\n" + exit +fi + +package_name="MNE-Python (dev)" +import_name="mne" + +# Run the import test outside of the repository, so we don't accidentally import the +# `mne` directory from there. This is an annoyance caused by MNE-Python's not using a +# src/ layout. +orig_dir=$(pwd) +cd ~ +if python -c "import $import_name" &> /dev/null; then + echo -e "āœ… $package_name is already installed.\n" + cd "${orig_dir}" + exit +else + cd "${orig_dir}" + code .devcontainer/Welcome.md + echo -e "šŸ’” $package_name is not installed. Installing now …\n" + pipx install uv + uv pip install -e ".[full-pyside6,dev,test_extra]" + echo -e "\nāœ… $package_name has been installed.\n" + echo -e "šŸ’” Installing pre-commit hooks …" + pre-commit install --install-hooks + echo -e "āœ… pre-commit hooks installed.\n" +fi + +echo -e "\nšŸš€ You're all set. Happy hacking!\n" diff --git a/.devcontainer/scripts/open-vnc-browser.sh b/.devcontainer/scripts/open-vnc-browser.sh new file mode 100755 index 00000000000..ab05a0e297c --- /dev/null +++ b/.devcontainer/scripts/open-vnc-browser.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -eu + +# Exit immediately if not running inside a Dev Container +if [ -z "${RUNNING_IN_DEV_CONTAINER+x}" ]; then + echo -e "šŸ‘‹ Not running in dev container, not opening web browser.\n" + exit +fi + +echo -e "šŸŒ Opening VNC desktop in web browser …\n" +xdg-open 'http://localhost:6080?autoconnect=true' +echo -e "Welcome to the MNE-Python Dev Container!\nCreate a plot in VS Code and it will show up here." | xmessage -center -timeout 30 -title "Welcome to MNE-Python!" -file - diff --git a/.gitignore b/.gitignore index 118eebd9c76..5e7275a1873 100644 --- a/.gitignore +++ b/.gitignore @@ -97,6 +97,7 @@ cover .venv/ venv/ *.json +!/.devcontainer/**/* !codemeta.json .hypothesis/ .ruff_cache/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000000..90744345fec --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Current File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": false, + "jinja": true + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000000..75d815807e0 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,36 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Install MNE-Python (dev)", + "type": "shell", + "command": ".devcontainer/scripts/install.sh", + "runOptions": { + "runOn": "folderOpen" + }, + "presentation": { + "showReuseMessage": false, + "reveal": "silent", + "close": true + }, + "problemMatcher": [] + }, + { + "label": "Open VNC Desktop in Browser", + "type": "shell", + "command": ".devcontainer/scripts/open-vnc-browser.sh", + "runOptions": { + "runOn": "folderOpen" + }, + "dependsOn": ["Install MNE-Python (dev)"], + "presentation": { + "showReuseMessage": false, + "reveal": "silent", + "close": true + }, + "problemMatcher": [] + } + ] +}