From 7d3a7a8c3df48bf9ae68de1917ae7868e4b0bf9c Mon Sep 17 00:00:00 2001 From: Moritz Heidkamp Date: Mon, 12 Sep 2022 23:13:11 +0200 Subject: [PATCH] Add :deps/prep-lib support for git dependencies Source dependencies may require a "prep" step e.g. to compile Java source[1]. This patch adds support for this but only for git dependencies since that's the only dependency type which implements this at the moment anyway[2]. This is achieved by extracing the necessary info from `deps.edn` to the lockfile. A second pass over the constructed Clojure home derived from the lockfile then picks out all the git libs which need prepping, runs the respective command for them and finally constructs a new home with the prepped libs. 1: See https://clojure.org/guides/deps_and_cli#prep_libs 2: Technically, :local/root dependencies also implement it but `clojure-nix-locker` doesn't have to handle these. --- createHome.nix | 73 +++++++++++++++++++++++++++++++++++--------------- default.nix | 28 +++++-------------- locker.py | 13 ++++++++- utils.nix | 25 +++++++++++++++++ 4 files changed, 95 insertions(+), 44 deletions(-) create mode 100644 utils.nix diff --git a/createHome.nix b/createHome.nix index 2a02f44..91564d0 100644 --- a/createHome.nix +++ b/createHome.nix @@ -22,7 +22,7 @@ let }; }; - handleGit = path: { url, rev, sha256, common_dir }: { + handleGit = path: { url, rev, sha256, common_dir, ... }: { name = path; path = pkgs.fetchgit { inherit url rev sha256; @@ -32,8 +32,10 @@ let # Corresponds to the ~/.m2/repository directory mavenRepoCache = pkgs.linkFarm "maven-repo-cache" (lib.mapAttrsToList fetchMaven contents.maven); + unpreppedGitWorkTrees = lib.mapAttrsToList handleGit contents.git; + # This corresponds to the ~/.gitlibs/libs directory, containing git worktrees - gitWorktreeCache = pkgs.linkFarm "git-worktree-cache" (lib.mapAttrsToList handleGit contents.git); + gitWorktreeCache = gitWorkTrees: pkgs.linkFarm "git-worktree-cache" gitWorkTrees; # This corresponds to the ~/.gitlibs/_repos directory, containing git directories for the above worktrees gitFakeRepoCache = pkgs.runCommandNoCC "git-fake-repo-cache" {} @@ -56,24 +58,53 @@ let echo '{}' > $out/tools/tools.edn ''; - # Creates the final home directory, combining all parts together - result = pkgs.linkFarm "clojure-home" [ - { - name = ".m2/repository"; - path = mavenRepoCache; - } - { - name = ".gitlibs/libs"; - path = gitWorktreeCache; - } - { - name = ".gitlibs/_repos"; - path = gitFakeRepoCache; - } + # Creates a home directory for Clojure, combining all parts together + clojureHome = gitWorkTrees: + pkgs.linkFarm "clojure-home" [ + { + name = ".m2/repository"; + path = mavenRepoCache; + } + { + name = ".gitlibs/libs"; + path = gitWorktreeCache gitWorkTrees; + } + { + name = ".gitlibs/_repos"; + path = gitFakeRepoCache; + } + { + name = ".clojure"; + path = configDir; + } + ]; + + unpreppedHome = clojureHome unpreppedGitWorkTrees; + + utils = import ./utils.nix { inherit pkgs; }; + + prepLib = { path, name }: spec: + if spec ? prep then + let prep = spec.prep; in + pkgs.runCommand "${name}-prepped" + { nativeBuildInputs = [ (utils.wrapClojure unpreppedHome pkgs.clojure) ]; } + '' + cp -r ${path} $out + chmod -R +w $out + cd $out + clojure -X:${prep.alias} ${prep.fn} + '' + else + path; + + prepGitWorkTree = { name, ... }@wt: { - name = ".clojure"; - path = configDir; - } - ]; + inherit name; + path = prepLib wt (lib.getAttr name contents.git); + }; + + preppedGitWorkTrees = builtins.map prepGitWorkTree unpreppedGitWorkTrees; + + preppedHome = clojureHome preppedGitWorkTrees; -in result +in preppedHome diff --git a/default.nix b/default.nix index 41b6a24..1f1c49d 100644 --- a/default.nix +++ b/default.nix @@ -8,6 +8,8 @@ let # We don't care about lines being too long flakeIgnore = [ "E501" ]; } ./locker.py; + + utils = import ./utils.nix { inherit pkgs; }; in { inherit standaloneLocker; @@ -28,6 +30,7 @@ in { commandLocker = command: pkgs.writeShellApplication { name = "clojure-nix-locker"; runtimeInputs = [ + pkgs.babashka pkgs.coreutils pkgs.git pkgs.gnutar @@ -70,26 +73,7 @@ in { homeDirectory = import ./createHome.nix { inherit pkgs src lockfile mavenRepos; }; - shellEnv = pkgs.writeTextFile { - name = "clojure-nix-locker.shell-env"; - text = '' - export HOME="${homeDirectory}" - export JAVA_TOOL_OPTIONS="-Duser.home=${homeDirectory}" - ''; - meta = { - description = '' - Can be sourced in shell scripts to export environment - variables so that `clojure` uses the locked dependencies. - ''; - }; - }; - wrapClojure = clojure: - (pkgs.runCommandNoCC "locked-clojure" { buildInputs = [ pkgs.makeWrapper ]; } '' - mkdir -p $out/bin - makeWrapper ${clojure}/bin/clojure $out/bin/clojure \ - --run "source ${shellEnv}" - makeWrapper ${clojure}/bin/clj $out/bin/clj \ - --run "source ${shellEnv}" - ''); - }; + shellEnv = utils.shellEnv homeDirectory; + wrapClojure = utils.wrapClojure homeDirectory; + }; } diff --git a/locker.py b/locker.py index 36d69a0..33daf52 100755 --- a/locker.py +++ b/locker.py @@ -29,6 +29,14 @@ gitlibsDir = args.home.joinpath('.gitlibs').resolve() +extractPrepLibInfo = ''' + (some-> *input* + :deps/prep-lib + (select-keys [:alias :fn]) + cheshire.core/generate-string + println) +''' + if gitlibsDir.exists(): for namespace_path in gitlibsDir.joinpath('libs').iterdir(): for name_path in namespace_path.iterdir(): @@ -36,7 +44,8 @@ path = rev_path.relative_to(gitlibsDir, "libs").as_posix() repo = Repo(rev_path) prefetch = subprocess.run(["nix-prefetch-git", rev_path], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True) - + with open(rev_path / "deps.edn") as deps_edn: + prep = subprocess.run(["bb", "-I", "--stream", extractPrepLibInfo], stdin=deps_edn, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True) result['git'][path] = { # This is the path to the corresponding bare repository in ~/.gitlibs/_repos "common_dir": Path(repo.common_dir).resolve().relative_to(gitlibsDir, "_repos").as_posix(), @@ -44,5 +53,7 @@ "rev": repo.head.commit.hexsha, "sha256": json.loads(prefetch.stdout)['sha256'], } + if preps := prep.stdout: + result['git'][path]['prep'] = json.loads(preps) print(json.dumps(result, indent=2, sort_keys=True)) diff --git a/utils.nix b/utils.nix new file mode 100644 index 0000000..c076ec3 --- /dev/null +++ b/utils.nix @@ -0,0 +1,25 @@ +{ pkgs }: + +rec { + shellEnv = homeDirectory: pkgs.writeTextFile { + name = "clojure-nix-locker.shell-env"; + text = '' + export HOME="${homeDirectory}" + export JAVA_TOOL_OPTIONS="-Duser.home=${homeDirectory}" + ''; + meta = { + description = '' + Can be sourced in shell scripts to export environment + variables so that `clojure` uses the locked dependencies. + ''; + }; + }; + wrapClojure = homeDirectory: clojure: + (pkgs.runCommandNoCC "locked-clojure" { buildInputs = [ pkgs.makeWrapper ]; } '' + mkdir -p $out/bin + makeWrapper ${clojure}/bin/clojure $out/bin/clojure \ + --run "source ${shellEnv homeDirectory}" + makeWrapper ${clojure}/bin/clj $out/bin/clj \ + --run "source ${shellEnv homeDirectory}" + ''); +}