diff --git a/.envrc b/.envrc index 3d4ddbc9..1530103c 100644 --- a/.envrc +++ b/.envrc @@ -2,4 +2,10 @@ if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" fi -use flake +watch_file .direnv/selected-shell + +if [ -e .direnv/selected-shell ]; then + use flake .#$(cat .direnv/selected-shell) +else + use flake +fi diff --git a/README.md b/README.md index 45c1270c..f600dbd7 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,8 @@ We welcome contributions from the community, and we've prepared a comprehensive - **Contribution Guidelines**: Find out how to contribute and make a meaningful impact on the cLAN project by reading [contributing.md](docs/contributing/contributing.md). Whether you're a newcomer or a seasoned developer, we look forward to your contributions and collaboration on the cLAN project. Let's build amazing things together! + + +### development environment +Setup `direnv` and `nix-direnv` and execute `dienv allow`. +To switch between different dev environments execute `select-shell`. diff --git a/devShell-python.nix b/devShell-python.nix new file mode 100644 index 00000000..d0a5b935 --- /dev/null +++ b/devShell-python.nix @@ -0,0 +1,107 @@ +{ + perSystem = + { pkgs + , self' + , lib + , ... + }: + let + python3 = pkgs.python3; + pypkgs = python3.pkgs; + clan-cli = self'.packages.clan-cli; + clan-vm-manager = self'.packages.clan-vm-manager; + pythonWithDeps = python3.withPackages ( + ps: + clan-cli.propagatedBuildInputs + ++ clan-cli.devDependencies + ++ [ + ps.pip + # clan-vm-manager deps + ps.pygobject3 + ] + ); + linuxOnlyPackages = lib.optionals pkgs.stdenv.isLinux [ + pkgs.xdg-utils + ]; + in + { + devShells.python = pkgs.mkShell { + inputsFrom = [ self'.devShells.default ]; + packages = + [ + pythonWithDeps + pypkgs.mypy + pypkgs.ipdb + pkgs.desktop-file-utils + pkgs.gtk4.dev + pkgs.ruff + pkgs.libadwaita.devdoc # has the demo called 'adwaita-1-demo' + ] + ++ linuxOnlyPackages + ++ clan-vm-manager.nativeBuildInputs + ++ clan-vm-manager.buildInputs + ++ clan-cli.nativeBuildInputs; + + PYTHONBREAKPOINT = "ipdb.set_trace"; + + shellHook = '' + ln -sfT ${clan-cli.nixpkgs} ./pkgs/clan-cli/clan_cli/nixpkgs + + ## PYTHON + + tmp_path=$(realpath ./.direnv) + repo_root=$(realpath .) + mkdir -p "$tmp_path/python/${pythonWithDeps.sitePackages}" + + # local dependencies + localPackages=( + $repo_root/pkgs/clan-cli + $repo_root/pkgs/clan-vm-manager + ) + + # Install the package in editable mode + # This allows executing `clan` from within the dev-shell using the current + # version of the code and its dependencies. + # TODO: this is slow. get rid of pip or add better caching + echo "==== Installing local python packages in editable mode ====" + for package in "''${localPackages[@]}"; do + ${pythonWithDeps}/bin/pip install \ + --quiet \ + --disable-pip-version-check \ + --no-index \ + --no-build-isolation \ + --prefix "$tmp_path/python" \ + --editable "$package" + done + + export PATH="$tmp_path/python/bin:$PATH" + export PYTHONPATH="''${PYTHONPATH:+$PYTHONPATH:}$tmp_path/python/${pythonWithDeps.sitePackages}" + # export PYTHONPATH="$tmp_path/python/${pythonWithDeps.sitePackages}" + + for package in "''${localPackages[@]}"; do + export PYTHONPATH="$package:$PYTHONPATH" + done + + if ! command -v xdg-mime &> /dev/null; then + echo "Warning: 'xdg-mime' is not available. The desktop file cannot be installed." + fi + + # install desktop file + set -eou pipefail + DESKTOP_FILE_NAME=lol.clan.vm.manager.desktop + DESKTOP_DST=~/.local/share/applications/$DESKTOP_FILE_NAME + DESKTOP_SRC=${clan-vm-manager.desktop-file}/share/applications/$DESKTOP_FILE_NAME + UI_BIN="clan-vm-manager" + + cp -f $DESKTOP_SRC $DESKTOP_DST + sleep 2 + sed -i "s|Exec=.*clan-vm-manager|Exec=$UI_BIN|" $DESKTOP_DST + xdg-mime default $DESKTOP_FILE_NAME x-scheme-handler/clan + echo "==== Validating desktop file installation ====" + set -x + desktop-file-validate $DESKTOP_DST + set +xeou pipefail + ''; + }; + }; +} diff --git a/devShell.nix b/devShell.nix index ef377370..ea8fa79d 100644 --- a/devShell.nix +++ b/devShell.nix @@ -4,9 +4,28 @@ , self' , config , ... - }: { + }: + let + writers = pkgs.callPackage ./pkgs/builders/script-writers.nix { }; + + ansiEscapes = { + reset = ''\033[0m''; + green = ''\033[32m''; + }; + + # a python program using argparse to enable and disable dev shells + # synopsis: select-shell enable|disable shell-name + # enabled devshells are written as a newline separated list into ./.direnv/selected-shells + select-shell = writers.writePython3Bin "select-shell" + { + flakeIgnore = [ "E501" ]; + } ./pkgs/scripts/select-shell.py; + in + { devShells.default = pkgs.mkShell { + # inputsFrom = [ self'.devShells.python ]; packages = [ + select-shell pkgs.tea self'.packages.tea-create-pr self'.packages.merge-after-ci @@ -17,6 +36,8 @@ shellHook = '' # no longer used rm -f "$(git rev-parse --show-toplevel)/.git/hooks/pre-commit" + + echo -e "${ansiEscapes.green}switch to another dev-shell using: select-shell${ansiEscapes.reset}" ''; }; }; diff --git a/flake.nix b/flake.nix index 41b87450..14c799a7 100644 --- a/flake.nix +++ b/flake.nix @@ -30,6 +30,7 @@ imports = [ ./checks/flake-module.nix ./devShell.nix + ./devShell-python.nix ./formatter.nix ./templates/flake-module.nix ./clanModules/flake-module.nix diff --git a/pkgs/clan-cli/pyproject.toml b/pkgs/clan-cli/pyproject.toml index eb046085..26eec53a 100644 --- a/pkgs/clan-cli/pyproject.toml +++ b/pkgs/clan-cli/pyproject.toml @@ -9,7 +9,7 @@ dynamic = ["version"] scripts = { clan = "clan_cli:main" } [tool.setuptools.packages.find] -exclude = ["clan_cli.nixpkgs*"] +exclude = ["clan_cli.nixpkgs*", "result"] [tool.setuptools.package-data] clan_cli = ["config/jsonschema/*", "webui/assets/**/*", "vms/mimetypes/**/*"] diff --git a/pkgs/clan-vm-manager/default.nix b/pkgs/clan-vm-manager/default.nix index 3923b0a2..dbcf52ba 100644 --- a/pkgs/clan-vm-manager/default.nix +++ b/pkgs/clan-vm-manager/default.nix @@ -13,6 +13,14 @@ }: let source = ./.; + desktop-file = makeDesktopItem { + name = "lol.clan.vm.manager"; + exec = "clan-vm-manager %u"; + icon = ./clan_vm_manager/assets/clan_white.png; + desktopName = "cLAN Manager"; + startupWMClass = "clan"; + mimeTypes = [ "x-scheme-handler/clan" ]; + }; in python3.pkgs.buildPythonApplication { name = "clan-vm-manager"; @@ -36,15 +44,18 @@ python3.pkgs.buildPythonApplication { propagatedBuildInputs = [ pygobject3 clan-cli ]; # also re-expose dependencies so we test them in CI - passthru.tests = { - clan-vm-manager-no-breakpoints = runCommand "clan-vm-manager-no-breakpoints" { } '' - if grep --include \*.py -Rq "breakpoint()" ${source}; then - echo "breakpoint() found in ${source}:" - grep --include \*.py -Rn "breakpoint()" ${source} - exit 1 - fi - touch $out - ''; + passthru = { + inherit desktop-file; + tests = { + clan-vm-manager-no-breakpoints = runCommand "clan-vm-manager-no-breakpoints" { } '' + if grep --include \*.py -Rq "breakpoint()" ${source}; then + echo "breakpoint() found in ${source}:" + grep --include \*.py -Rn "breakpoint()" ${source} + exit 1 + fi + touch $out + ''; + }; }; # Don't leak python packages into a devshell. @@ -56,13 +67,6 @@ python3.pkgs.buildPythonApplication { PYTHONPATH= $out/bin/clan-vm-manager --help ''; desktopItems = [ - (makeDesktopItem { - name = "lol.clan.vm.manager"; - exec = "clan-vm-manager %u"; - icon = ./clan_vm_manager/assets/clan_white.png; - desktopName = "cLAN Manager"; - startupWMClass = "clan"; - mimeTypes = [ "x-scheme-handler/clan" ]; - }) + desktop-file ]; } diff --git a/pkgs/clan-vm-manager/pyproject.toml b/pkgs/clan-vm-manager/pyproject.toml index b2116366..800b4661 100644 --- a/pkgs/clan-vm-manager/pyproject.toml +++ b/pkgs/clan-vm-manager/pyproject.toml @@ -9,6 +9,9 @@ name = "clan-vm-manager" dynamic = ["version"] scripts = { clan-vm-manager = "clan_vm_manager:main" } +[tool.setuptools.packages.find] +exclude = ["result"] + [tool.setuptools.package-data] clan_vm_manager = ["**/assets/*"] diff --git a/pkgs/scripts/select-shell.py b/pkgs/scripts/select-shell.py new file mode 100644 index 00000000..c2542d9a --- /dev/null +++ b/pkgs/scripts/select-shell.py @@ -0,0 +1,39 @@ +import argparse +import json +import pathlib +import subprocess +import sys + +parser = argparse.ArgumentParser(description="Select a devshell") +parser.add_argument("shell", help="the name of the devshell to select", nargs="?") +parser.add_argument("--list", action="store_true", help="list available devshells") +args = parser.parse_args() + +selected_shell_file = pathlib.Path(".direnv/selected-shell") + +if not args.list and not args.shell: + parser.print_help() + exit(0) +if args.list: + flake_show = subprocess.run( + ["nix", "flake", "show", "--json", "--no-write-lock-file"], + stdout=subprocess.PIPE, + ) + data = json.loads(flake_show.stdout.decode()) + print("Available devshells:") + print("\n".join(data["devShells"]["x86_64-linux"].keys())) + exit(0) +if selected_shell_file.exists(): + with open(selected_shell_file) as f: + current_shell = f.read().strip() +else: + current_shell = "" + +if current_shell == args.shell: + print(f"{args.shell} devshell already selected. No changes made.") + sys.exit(0) + +with open(selected_shell_file, "w") as f: + f.write(args.shell) + +print(f"{args.shell} devshell selected")