Add an Elisp script to convert Nix config to Elisp code

This commit is contained in:
Sameer Rahmani 2024-04-14 19:49:21 +01:00
parent 426c1d2dd1
commit c9cdb0a83e
Signed by: lxsameer
GPG Key ID: 8741FACBF412FFA5
11 changed files with 233 additions and 388 deletions

98
lisp/build.el Normal file
View File

@ -0,0 +1,98 @@
;;; FG42 --- The mighty editor for the emacsians -*- lexical-binding: t; -*-
;;
;; Copyright (c) 2010-2024 Sameer Rahmani <lxsameer@gnu.org>
;;
;; Author: Sameer Rahmani <lxsameer@gnu.org>
;; URL: https://devheroes.codes/FG42/FG42
;; Version: 4.0.0
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;
;;; Commentary:
;; This is the builder script that takes the config.json from the input
;; (typically provided by Nix). And generates appropriate Elisp file
;; for the main FG42 Elsip code to load and use.
;;
;; Please note that this file runs with Emacs and not FG42.
;;; Code:
(when (not (json-available-p))
(error "Error: libjasson support is missing"))
;; Elisp's hashtables are not functional so I have to store the
;; state like this, Ewwwwww!
(defvar build-state '()
"A list of expressions that will dump in the output at the end.")
(defmacro with-json (var &rest body)
"Read the json file from cli arguments, user VAR run the BODY on it."
(declare (indent defun))
`(let ((input-file (car command-line-args-left)))
(message "Reading the input file: %s" input-file)
(with-temp-buffer
(insert-file-contents input-file)
(let ((,var (json-parse-buffer)))
,@body))))
(defun generate-compile-time-requires (elisp-pkgs)
"Generate a list of `require' forms of ELISP-PKGS for compile time."
(when (> (length elisp-pkgs) 0)
(add-to-list
'build-state
`(eval-when-compile
,@(mapcar (lambda (pkg) `(require ',(intern pkg))) elisp-pkgs)))))
(defun generate-generic-vars (k v)
"Generate a list of vars for K and value V."
(add-to-list 'build-state `(defvar ,(intern (format "fg42/%s" k)) ,v)))
(defun fg42-config-key-handler (k v)
"Handle the FG42 config K and its value V."
(cond
((string= k "requires")
(generate-compile-time-requires v))
(t (generate-generic-vars k v))))
(defun fg42-handler (config)
"Handle the CONFIG for FG42.
CONFIG maps to the collective `config' value of Nix modules."
(maphash #'fg42-config-key-handler config))
(defun handle-conifg-for (k v)
"Find the handler of K and its value V to it."
(funcall (intern (format "%s-handler" k)) v))
(defun handle-top-level (k v)
"Handle the key K and value V form the top level of the JSON data."
(when (not (string= k "fg42"))
(error "Don't know how to handle '%s'" k))
(handle-conifg-for k v))
(with-json j
;; TODO: To make it scale, may be let Nix modules
;; plug into this script. There is a security
;; rist though
(maphash #'handle-top-level j)
(with-temp-file (cadr command-line-args-left)
(mapc (lambda (x) (print x (current-buffer))) build-state)))
(provide 'build)
;;; build.el ends here

View File

@ -1,219 +0,0 @@
# Fg42 - Emacs Editor for advance users
#
# Copyright (c) 2010-2024 Sameer Rahmani <lxsameer@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
{ lib
, config
, stdenv
, git
, texinfo
, gcc
, bash
, utils
, emacsPackagesFor
, makeFontsConf
, xorg
, maintainers
, writeText
, writeShellApplication
, ...
}:
with builtins;
let
cfg = config.fg42;
emacs = cfg.emacs;
version = cfg.version;
emacsBundle = (emacsPackagesFor emacs).withPackages (_: cfg.elispPackages);
paths = map (x: "${x}/bin/") cfg.paths;
pathsStr = lib.strings.concatStrings (lib.strings.intersperse ":" paths);
mimeTypes = builtins.concatStringsSep ";" cfg.mimeTypes;
fontsConf = makeFontsConf { fontDirectories = cfg.fonts; };
# Whatever that needs to be in the PATH is considered a runtime
# dependency.
# Fonts are obviously runtime dependency as well
runtimeDependencies = cfg.paths ++ cfg.fonts ++ cfg.elispPackages;
buildConfig = generateBuildConfig cfg.buildConfig;
addToList = epkg: ''(add-to-list 'directories-to-autogen "${epkg}")'';
sexprs = [
''
(require 'loaddefs-gen)
(defvar directories-to-autogen '())
(defvar output-file (getenv "LOADEF_OUTPUT"))
''
] ++ (map addToList cfg.elispPackages) ++ [
''
(message ">> %s ----- %s" directories-to-autogen output-file)
(loaddefs-generate directories-to-autogen
output-file)
(provide 'loaddef-generator)
''
];
loaddefGenerator = writeText "loaddef-generator.el" (concatStringsSep "\n" sexprs);
loaddefScript = writeShellApplication {
name = "loaddef";
text = ''
#!${stdenv.shell}
set +x
LOADEF_OUTPUT="$1" ${emacsBundle}/bin/emacs -Q -q --batch -l ${loaddefGenerator}
'';
};
startupPackage = cfg.startUp;
# elsipFiles = map (file: ) cfg.elispPackages;
# nativeCompiler = epkgs: ''
# emacs -L . --batch -f batch-native-compile ${elispFiles}
# '';
in
stdenv.mkDerivation rec {
inherit version;
pname = "FG42";
src = ../../.;
buildPhase = ''
runHook preBuild
LISPDIR=$out/share/fg42/
mkdir -p $out/bin
mkdir -p $out/share/applications/
install -d $LISPDIR
cp -rv ${src}/share $out/
cp -rv ${src}/snippets $LISPDIR/snippets
cp "${fontsConf}" $LISPDIR/fonts.conf
chmod 755 $LISPDIR -R
export FONTCONFIG_FILE="$LISPDIR/fonts.conf"
cat >> $out/share/applications/FG42.desktop << EOF
[Desktop Entry]
Encoding=UTF-8
Name=FG42
GenericName=FG42
Comment=The nix base Emacs bundle for advance users
MimeType=${mimeTypes}
Type=Application
Terminal=false
Categories=Development;TextEditor;
StartupWMClass=FG42
Exec=${placeholder "out"}/bin/fg42
Icon=fg42
Version=${version}
EOF
cat >> $out/share/runtime_deps << EOF
${lib.strings.concatLines runtimeDependencies}
${emacsBundle}
EOF
cd $LISPDIR
#compile stuff
cd -
cat >> $out/bin/fg42 << EOF
#!${stdenv.shell}
export PATH=${pathsStr}:$PATH
export FONTCONFIG_FILE="$LISPDIR/fonts.conf"
LIBRARY_PATH="\$(${stdenv.cc}/bin/cc -print-file-name=libgccjit.so):\$LIBRARY_PATH" \
FG42_WM=fales ${emacsBundle}/bin/emacs \
--name FG42 \
-q --no-splash --title FG42 \
-l ${cfg.startUp} "\$@"
EOF
chmod +x $out/bin/fg42
LISPDIR=$out/share/fg42/lisp/
mkdir -p $LISPDIR
#emacs --batch -l package --eval "(package-generate-autoloads \"${pname}\" \"$LISPDIR\")"
# ${loaddefScript}/bin/loaddef ${placeholder "out"}/share/fg42/lisp/loaddefs.el
runHook postBuild
cat >> $out/bin/fg42-wm << EOF
#!${stdenv.shell}
export FG42_HOME=${placeholder "out"}/share/fg42/
export FG42_EMACSD=~/.fg42/v4/emacs.d
export FG42_USE_NIX=true;
export PATH=${pathsStr}:\$PATH
export FONTCONFIG_FILE="$LISPDIR/fonts.conf"
# Disable access control for the current user.
${xorg.xhost}/bin/xhost +SI:localuser:\$USER
# Make Java applications aware this is a non-reparenting window manager.
export _JAVA_AWT_WM_NONREPARENTING=1
# Set default cursor.
xsetroot -cursor_name left_ptr
# Set keyboard repeat rate.
xset r rate 400 30
# Uncomment the following block to use the exwm-xim module.
# export XMODIFIERS=@im=exwm-xim
# export GTK_IM_MODULE=xim
# export QT_IM_MODULE=xim
# export CLUTTER_IM_MODULE=xim
LIBRARY_PATH="\$(${stdenv.cc}/bin/cc -print-file-name=libgccjit.so):\$LIBRARY_PATH" \
FG42_WM=true ${emacsBundle}/bin/emacs \
--name FG42 \
-q --no-splash --title FG42 \
-l \$FG42_HOME/lisp/fg42/fg42.el "\$@"
EOF
chmod +x $out/bin/fg42-wm
runHook postBuild
'';
buildInputs = [ emacs emacsBundle git texinfo gcc bash ];
addEmacsNativeLoadPath = true;
meta = {
broken = false;
platforms = emacs.meta.platforms;
homepage = "https://fg42.org/";
maintainers = [ maintainers.lxsameer ];
description = "The mighty editor for the Emacsians";
longDescription = ''
FG42 is a framework to create and editor and window manager based on GNU/Emacs.
It has a pre-defined setup as well which can be installed out of the box. But the
goal of this project is to provide the API necessary to create an integrated editor.
So you need to know about Emacs in advance.
'';
license = lib.licenses.gpl3Plus;
};
}

View File

@ -1 +0,0 @@
lxsameer@majin.69922:1712871165

View File

@ -25,7 +25,7 @@
, makeFontsConf
, xorg
, maintainers
, writeText
, writeTextFile
, writeShellApplication
, ...
}:
@ -50,35 +50,41 @@ let
# Fonts are obviously runtime dependency as well
runtimeDependencies = cfg.paths ++ cfg.fonts ++ cfg.elispPackages;
buildConfig = generateBuildConfig cfg.buildConfig;
# addToList = epkg: ''(add-to-list 'directories-to-autogen "${epkg}")'';
addToList = epkg: ''(add-to-list 'directories-to-autogen "${epkg}")'';
# sexprs = [
# ''
# (require 'loaddefs-gen)
# (defvar directories-to-autogen '())
# (defvar output-file (getenv "LOADEF_OUTPUT"))
# ''
# ] ++ (map addToList cfg.elispPackages) ++ [
# ''
# (message ">> %s ----- %s" directories-to-autogen output-file)
# (loaddefs-generate directories-to-autogen
# output-file)
# (provide 'loaddef-generator)
# ''
# ];
# loaddefGenerator = writeText "loaddef-generator.el" (concatStringsSep "\n" sexprs);
# loaddefScript = writeShellApplication {
# name = "loaddef";
sexprs = [
''
(require 'loaddefs-gen)
(defvar directories-to-autogen '())
(defvar output-file (getenv "LOADEF_OUTPUT"))
''
] ++ (map addToList cfg.elispPackages) ++ [
''
(message ">> %s ----- %s" directories-to-autogen output-file)
(loaddefs-generate directories-to-autogen
output-file)
(provide 'loaddef-generator)
''
];
loaddefGenerator = writeText "loaddef-generator.el" (concatStringsSep "\n" sexprs);
loaddefScript = writeShellApplication {
name = "loaddef";
# text = ''
# #!${stdenv.shell}
# set +x
# LOADEF_OUTPUT="$1" ${emacsBundle}/bin/emacs -Q -q --batch -l ${loaddefGenerator}
# '';
# };
text = ''
#!${stdenv.shell}
set +x
LOADEF_OUTPUT="$1" ${emacsBundle}/bin/emacs -Q -q --batch -l ${loaddefGenerator}
'';
configFile = writeTextFile {
name = "config.json";
text = (toJSON config);
executable = false;
};
startupPackage = cfg.startUp;
@ -87,7 +93,6 @@ let
# emacs -L . --batch -f batch-native-compile ${elispFiles}
# '';
in
stdenv.mkDerivation rec {
inherit version;
pname = "FG42";
@ -103,15 +108,20 @@ stdenv.mkDerivation rec {
install -d $LISPDIR
# cp -rv ${src}/lisp/ $LISPDIR
mkdir -p $LISPDIR/lisp
cp -rv ${src}/lisp/build.el $LISPDIR/lisp
cp -rv ${src}/share $out/
cp -rv ${src}/snippets $LISPDIR/snippets
cp "${fontsConf}" $LISPDIR/fonts.conf
chmod 755 $LISPDIR -R
cp "${configFile}" $LISPDIR/config.json
export FONTCONFIG_FILE="$LISPDIR/fonts.conf"
chmod 755 $LISPDIR -R
emacs --batch -l $LISPDIR/lisp/build.el $LISPDIR/config.json $LISPDIR/lisp/fg42_init.el
cat >> $out/share/applications/FG42.desktop << EOF
[Desktop Entry]
@ -156,7 +166,6 @@ stdenv.mkDerivation rec {
LISPDIR=$out/share/fg42/lisp/
mkdir -p $LISPDIR
#emacs --batch -l package --eval "(package-generate-autoloads \"${pname}\" \"$LISPDIR\")"
# ${loaddefScript}/bin/loaddef ${placeholder "out"}/share/fg42/lisp/loaddefs.el
runHook postBuild

View File

@ -44,6 +44,17 @@ with lib;
'';
};
fg42.requires = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
A list of Emacs packages to preload in compile time.
In general you want your entry point module in this list
to setup your autoloads, hooks, and everything.
'';
};
fg42.paths = mkOption {
type = types.listOf types.package;
default = [ ];
@ -69,6 +80,12 @@ with lib;
'';
};
fg42.font = mkOption {
type = types.uniq types.str;
example = "fg42.font = ''(cons \"Fira Code\" 10) '';";
description = "The default font for FG42";
};
fg42.startUp = mkOption {
type = types.uniq types.path;
description = "The main startup file to load first. This is usually fg42.el";

View File

@ -1,122 +0,0 @@
# Fg42 - Emacs Editor for advance users
#
# Copyright (c) 2010-2024 Sameer Rahmani <lxsameer@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This is the home manager module that exposes FG42. It differs
# from FG42 modules that are structurally the same but used in
# different context
# A list of default FG42 modules to build FG42 with.
{ lib, config, pkgs, ... }:
with lib;
let
generateBuildConfig = configs:
let
defineConfig = cs: name:
let
config = builtins.getAttr name cs;
form = if config.const then "defconst" else "defvar";
in
''(${form} ${name} ${config.value} "${config.docs}")'';
configsList = builtins.map (defineConfig configs) (builtins.attrNames configs);
in
pkgs.writeTextFile {
name = "build-config";
text = ''
${builtins.concatStringsSep "\n" configsList}
(provide 'fg42/build-config)
'';
executable = false;
};
buildConfig = generateBuildConfig (config.fg42.buildConfig);
drv = pkgs.emacsPackages.trivialBuild rec {
pname = "fg42-build-config";
version = config.fg42.version;
src = ./.;
buildPhase = ''
runHook preBuild
LISPDIR=$out/share/emacs/site-lisp/fg42
install -d $LISPDIR
cp -v ${buildConfig} $LISPDIR/build-config.el
emacs -L . --batch -f batch-byte-compile *.el
runHook postBuild
'';
installPhase = ''
runHook preInstall
LISPDIR=$out/share/emacs/site-lisp/fg42/
emacs --batch -l package --eval "(package-generate-autoloads \"${pname}\" \"$LISPDIR\")"
runHook postInstall
'';
};
in
{
options = {
fg42.buildConfig = mkOption {
type = types.attrsOf
(types.submodule {
options = {
value = mkOption {
type = types.str;
default = "nil";
description = ''
The value should be quoted. For example the string "nil"
means the actual nil in elisp and the string "\"foo\""
means elisp string "foo".
'';
};
docs = mkOption {
type = types.str;
description = "The docstring for the variable or constant (mandatory)";
};
const = mkOption {
type = types.bool;
default = false;
description = "defvar vs defconst";
};
};
});
default = { };
example = ''
fg42.config = {
"fg42/some-var" = {
value = "foo";
docs = "The docstring";
const = false;
};
};
'';
description = ''
An attrset of configuration variables and their values
that should end up in `fg42/config` elisp module.
'';
};
};
config = {
fg42.elispPackages = [ drv ];
};
}

View File

@ -22,7 +22,6 @@
{ pkgs, lib }:
let
modules = [
./build_config
./editor
];

View File

@ -49,7 +49,6 @@ let
dicts = pkgs.aspellWithDicts (dicts: with dicts; [ en en-computers en-science ]);
in
{
config = {
fg42.elispPackages = [ drv ] ++ deps;
fg42.startUp = lib.mkDefault "${drv}/share/emacs/site-lisp/fg42.el";
@ -66,14 +65,7 @@ in
dicts
]);
fg42.buildConfig = {
"fg42/font" = {
value = '''("Fira Mono" 11)'';
docs = ''
The default font for FG42. You can override it
in your configuration file.
'';
};
};
fg42.font = lib.mkDefault '''("Fira Mono" 11)'';
fg42.requires = [ "fg42/cpp" ];
};
}

View File

@ -106,16 +106,18 @@
(defun fg42/initialize ()
"Initialize FG42 after the Emacs window is rendered by loading the user init file."
;; (fg42/-startup-optimization)
(require 'fg42/editor)
(require 'fg42/editor)
;; Since the editor module imports all the submodules
;; on compile time we should be good to assume
;; everything that needed to be loaded or autoloaded
;; are there already.
(when (file-exists-p user-init-file)
(load user-init-file))
(fg42/setup-theme!)
(fg42/setup-editor)
;; ;; If user didn't select a theme, load the default stuff
;; (when (not (featurep 'fg42/themes))
;; (require 'fg42/themes))
(add-hook 'emacs-startup-hook
(lambda ()
(run-hooks 'fg42/after-init-hook)

View File

@ -26,6 +26,7 @@
;; We build this file via the main FG42's Nix derivation. It
;; contains the final Nix configuration of FG42.
(require 'fg42/build-config)
(require 'fg42/themes)
;; ;; Language support
;; (require 'fg42/autocomplete)
;; (require 'fg42/langs/langs)

View File

@ -0,0 +1,69 @@
;;; Themes --- Theme library of FG42 -*- lexical-binding: t; -*-
;;
;; Copyright (c) 2010-2024 Sameer Rahmani & Contributors
;;
;; Author: Sameer Rahmani <lxsameer@gnu.org>
;; URL: https://devheroes.codes/FG42/FG42
;; Version: 4.0.0
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;
;;; Commentary:
;; Cubes are the building blocks of any `FG42' editor. Each `cube' is a
;; unit which defines different abilities in a deterministic and idempotent
;; way. Cubes are composable and a composition of cubes creates an editor.
;;
;;; Code:
(eval-when-compile
(require 'fpkg)
(require 'fg42/build-config))
(require 'fg42/utils)
(defvar fg42/ui-hook ()
"A hook that cubes can use via :ui-hook property.
It executes way before the rest of the cubes.")
(defvar fg42/before-initializing-theme-hook ()
"The hook to plug any configuration to before initialize event of themes.")
(defvar fg42/after-initializing-theme-hook ()
"The hook to plug any configuration to after initialize event of themes.")
(defmacro fg42/setup-theme! (&rest body)
"Grab the them provided by `fg42/theme' and load it.
Load the theme via `use!' and pass the BODY to the `use!' macroro.
If the theme name and the theme package name are different the package
name can be set via `fg42/theme-package'".
(declare (indent defun))
(let ((pkg (if (not (null fg42/theme-package))
fg42/theme-package
fg42/theme)))
`(use! ,pkg
"Setting up the ,pkg package."
:config
(load-theme fg42/theme t)
,@body)))
;; (use! base16-theme
;; "Load base16 based themes in FG42."
;; :config
;; (load-theme 'base16-eighties t))
(provide 'fg42/themes)
;;; themes.el ends here