From 9cb746a43b761cecf78ed1df1bb7a5266ff7d8fe Mon Sep 17 00:00:00 2001
From: Serge Bazanski <q3k@q3k.org>
Date: Sat, 13 May 2023 00:45:18 +0200
Subject: [PATCH] nix: initial support

This adds initial support for building on Nix(OS). It can be tested by
following the updated README.md. The result should be the ability to
build the firmware without having to download any other dependencies.
---
 README.md        |  18 ++++-
 nix/shell.nix    |  24 ++++++
 nix/sources.json |  26 +++++++
 nix/sources.nix  | 198 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 262 insertions(+), 4 deletions(-)
 create mode 100644 nix/shell.nix
 create mode 100644 nix/sources.json
 create mode 100644 nix/sources.nix

diff --git a/README.md b/README.md
index 5bd5c7a375..49d6ea838b 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,9 @@ mpremote fs cp python_payload/boot.py :boot.py
 mpremote fs cp python_payload/cap_touch_demo.py :cap_touch_demo.py
 ```
 
-## how to build
+## how to install dependencies
+
+### Generic
 
 1. install esp-idf v4.4:
 (copied from https://www.wemos.cc/en/latest/tutorials/others/build_micropython_esp32.html)
@@ -49,14 +51,22 @@ $ source export.sh
 best put something like "alias espidf='source ~/esp-idf/export.sh'" in your .bashrc etc,
 you need to run it in every new terminal and adding it to autostart did bother us
 
-2. prepare build
+### Nix(OS)
+
+```
+$ nix-shell nix/shell.nix
+```
+
+## how to build
+
+1. prepare build
 ```
 $ cd micropython/
 $ make -C mpy-cross
 $ cd ports/esp32
 $ make submodules
 ```
-3. build/flash
+2. build/flash
 make sure esp-idf is sourced as in step 1 and that you are in micropython/ports/esp32
 build:
 ```
@@ -73,7 +83,7 @@ empty build cache (useful when moving files around):
 $ make clean
 ```
 
-4. access micropython repl:
+3. access micropython repl:
 ```
 $ picocom -b 115200 /dev/ttyACM0
 ```
diff --git a/nix/shell.nix b/nix/shell.nix
new file mode 100644
index 0000000000..5c2fb28d69
--- /dev/null
+++ b/nix/shell.nix
@@ -0,0 +1,24 @@
+let
+  sources = import ./sources.nix;
+  nixpkgs = import sources.nixpkgs {
+    overlays = [
+      (import "${sources.nixpkgs-esp-dev}/overlay.nix")
+    ];
+  };
+
+in with nixpkgs; pkgs.mkShell {
+  name = "badg23-shell";
+  buildInputs = with pkgs; [
+    gcc-xtensa-esp32s3-elf-bin
+    openocd-esp32-bin
+    esp-idf
+    esptool
+
+    git wget gnumake
+    flex bison gperf pkgconfig
+
+    cmake ninja
+
+    ncurses5
+  ];
+}
diff --git a/nix/sources.json b/nix/sources.json
new file mode 100644
index 0000000000..69aba976b5
--- /dev/null
+++ b/nix/sources.json
@@ -0,0 +1,26 @@
+{
+    "nixpkgs": {
+        "branch": "master",
+        "description": "Nix Packages collection",
+        "homepage": "",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "c11d08f02390aab49e7c22e6d0ea9b176394d961",
+        "sha256": "017jdga9j0qkdplly8pypxxcsski0h9galzaf9qsvpq0j50x86cv",
+        "type": "tarball",
+        "url": "https://github.com/NixOS/nixpkgs/archive/c11d08f02390aab49e7c22e6d0ea9b176394d961.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    },
+    "nixpkgs-esp-dev": {
+        "branch": "master",
+        "description": "Nix flake and overlay for ESP8266 and ESP32 development.",
+        "homepage": "",
+        "owner": "mirrexagon",
+        "repo": "nixpkgs-esp-dev",
+        "rev": "48413ee362b4d0709e1a0dff6aba7fd99060335e",
+        "sha256": "1n0m1nr39ngff6px9gdm9hkgzqq7imdcakpgz9md7fmm5c7b309f",
+        "type": "tarball",
+        "url": "https://github.com/mirrexagon/nixpkgs-esp-dev/archive/48413ee362b4d0709e1a0dff6aba7fd99060335e.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    }
+}
diff --git a/nix/sources.nix b/nix/sources.nix
new file mode 100644
index 0000000000..fe3dadf7eb
--- /dev/null
+++ b/nix/sources.nix
@@ -0,0 +1,198 @@
+# This file has been generated by Niv.
+
+let
+
+  #
+  # The fetchers. fetch_<type> fetches specs of type <type>.
+  #
+
+  fetch_file = pkgs: name: spec:
+    let
+      name' = sanitizeName name + "-src";
+    in
+    if spec.builtin or true then
+      builtins_fetchurl { inherit (spec) url sha256; name = name'; }
+    else
+      pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
+
+  fetch_tarball = pkgs: name: spec:
+    let
+      name' = sanitizeName name + "-src";
+    in
+    if spec.builtin or true then
+      builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
+    else
+      pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
+
+  fetch_git = name: spec:
+    let
+      ref =
+        spec.ref or (
+          if spec ? branch then "refs/heads/${spec.branch}" else
+          if spec ? tag then "refs/tags/${spec.tag}" else
+          abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"
+        );
+      submodules = spec.submodules or false;
+      submoduleArg =
+        let
+          nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0;
+          emptyArgWithWarning =
+            if submodules
+            then
+              builtins.trace
+                (
+                  "The niv input \"${name}\" uses submodules "
+                  + "but your nix's (${builtins.nixVersion}) builtins.fetchGit "
+                  + "does not support them"
+                )
+                { }
+            else { };
+        in
+        if nixSupportsSubmodules
+        then { inherit submodules; }
+        else emptyArgWithWarning;
+    in
+    builtins.fetchGit
+      ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg);
+
+  fetch_local = spec: spec.path;
+
+  fetch_builtin-tarball = name: throw
+    ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
+        $ niv modify ${name} -a type=tarball -a builtin=true'';
+
+  fetch_builtin-url = name: throw
+    ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
+        $ niv modify ${name} -a type=file -a builtin=true'';
+
+  #
+  # Various helpers
+  #
+
+  # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
+  sanitizeName = name:
+    (
+      concatMapStrings (s: if builtins.isList s then "-" else s)
+        (
+          builtins.split "[^[:alnum:]+._?=-]+"
+            ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
+        )
+    );
+
+  # The set of packages used when specs are fetched using non-builtins.
+  mkPkgs = sources: system:
+    let
+      sourcesNixpkgs =
+        import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
+      hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
+      hasThisAsNixpkgsPath = <nixpkgs> == ./.;
+    in
+    if builtins.hasAttr "nixpkgs" sources
+    then sourcesNixpkgs
+    else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
+      import <nixpkgs> { }
+    else
+      abort
+        ''
+          Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
+          add a package called "nixpkgs" to your sources.json.
+        '';
+
+  # The actual fetching function.
+  fetch = pkgs: name: spec:
+
+    if ! builtins.hasAttr "type" spec then
+      abort "ERROR: niv spec ${name} does not have a 'type' attribute"
+    else if spec.type == "file" then fetch_file pkgs name spec
+    else if spec.type == "tarball" then fetch_tarball pkgs name spec
+    else if spec.type == "git" then fetch_git name spec
+    else if spec.type == "local" then fetch_local spec
+    else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
+    else if spec.type == "builtin-url" then fetch_builtin-url name
+    else
+      abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
+
+  # If the environment variable NIV_OVERRIDE_${name} is set, then use
+  # the path directly as opposed to the fetched source.
+  replace = name: drv:
+    let
+      saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name;
+      ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
+    in
+    if ersatz == "" then drv else
+      # this turns the string into an actual Nix path (for both absolute and
+      # relative paths)
+    if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
+
+  # Ports of functions for older nix versions
+
+  # a Nix version of mapAttrs if the built-in doesn't exist
+  mapAttrs = builtins.mapAttrs or (
+    f: set: with builtins;
+    listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
+  );
+
+  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
+  range = first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1);
+
+  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
+  stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
+
+  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
+  stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
+  concatMapStrings = f: list: concatStrings (map f list);
+  concatStrings = builtins.concatStringsSep "";
+
+  # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
+  optionalAttrs = cond: as: if cond then as else { };
+
+  # fetchTarball version that is compatible between all the versions of Nix
+  builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
+    let
+      inherit (builtins) lessThan nixVersion fetchTarball;
+    in
+    if lessThan nixVersion "1.12" then
+      fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; }))
+    else
+      fetchTarball attrs;
+
+  # fetchurl version that is compatible between all the versions of Nix
+  builtins_fetchurl = { url, name ? null, sha256 }@attrs:
+    let
+      inherit (builtins) lessThan nixVersion fetchurl;
+    in
+    if lessThan nixVersion "1.12" then
+      fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; }))
+    else
+      fetchurl attrs;
+
+  # Create the final "sources" from the config
+  mkSources = config:
+    mapAttrs
+      (
+        name: spec:
+          if builtins.hasAttr "outPath" spec
+          then
+            abort
+              "The values in sources.json should not have an 'outPath' attribute"
+          else
+            spec // { outPath = replace name (fetch config.pkgs name spec); }
+      )
+      config.sources;
+
+  # The "config" used by the fetchers
+  mkConfig =
+    { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
+    , sources ? if sourcesFile == null then { } else builtins.fromJSON (builtins.readFile sourcesFile)
+    , system ? builtins.currentSystem
+    , pkgs ? mkPkgs sources system
+    }: rec {
+      # The sources, i.e. the attribute set of spec name to spec
+      inherit sources;
+
+      # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
+      inherit pkgs;
+    };
+
+in
+mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); }
-- 
GitLab