mirror of
https://github.com/nix-community/dream2nix.git
synced 2024-12-29 17:33:46 +03:00
Merge pull request #317 from leungbk/racket-impure
Racket: add impure translator and simple builder
This commit is contained in:
commit
a5bf4443bb
18
examples/racket/flake.nix
Normal file
18
examples/racket/flake.nix
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
inputs = {
|
||||
dream2nix.url = "github:nix-community/dream2nix";
|
||||
|
||||
goblins.url = "gitlab:leungbk/goblins";
|
||||
goblins.flake = false;
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
dream2nix,
|
||||
goblins,
|
||||
} @ inp: (dream2nix.lib.makeFlakeOutputs {
|
||||
systems = ["x86_64-linux"];
|
||||
config.projectRoot = ./.;
|
||||
source = goblins;
|
||||
});
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
nodejs = "granular";
|
||||
python = "simple-builder";
|
||||
php = "granular";
|
||||
racket = "simple";
|
||||
};
|
||||
|
||||
# TODO
|
||||
|
@ -11,6 +11,7 @@
|
||||
php = "granular-php";
|
||||
haskell = "simple-haskell";
|
||||
debian = "simple-debian";
|
||||
racket = "simple-racket";
|
||||
};
|
||||
loader = b: b // {build = callPackageDream b.build {};};
|
||||
funcs = config.functions.subsystem-loading;
|
||||
|
110
src/subsystems/racket/builders/simple-racket/default.nix
Normal file
110
src/subsystems/racket/builders/simple-racket/default.nix
Normal file
@ -0,0 +1,110 @@
|
||||
{...}: {
|
||||
type = "pure";
|
||||
|
||||
build = {
|
||||
lib,
|
||||
pkgs,
|
||||
stdenv,
|
||||
# dream2nix inputs
|
||||
externals,
|
||||
...
|
||||
}: {
|
||||
### FUNCTIONS
|
||||
# AttrSet -> Bool) -> AttrSet -> [x]
|
||||
getCyclicDependencies, # name: version: -> [ {name=; version=; } ]
|
||||
getDependencies, # name: version: -> [ {name=; version=; } ]
|
||||
getSource, # name: version: -> store-path
|
||||
# to get information about the original source spec
|
||||
getSourceSpec, # name: version: -> {type="git"; url=""; hash="";}
|
||||
### ATTRIBUTES
|
||||
subsystemAttrs, # attrset
|
||||
defaultPackageName, # string
|
||||
defaultPackageVersion, # string
|
||||
# all exported (top-level) package names and versions
|
||||
# attrset of pname -> version,
|
||||
packages,
|
||||
# all existing package names and versions
|
||||
# attrset of pname -> versions,
|
||||
# where versions is a list of version strings
|
||||
packageVersions,
|
||||
# function which applies overrides to a package
|
||||
# It must be applied by the builder to each individual derivation
|
||||
# Example:
|
||||
# produceDerivation name (mkDerivation {...})
|
||||
produceDerivation,
|
||||
...
|
||||
} @ args: let
|
||||
l = lib // builtins;
|
||||
|
||||
# packages to export
|
||||
exportedPackages =
|
||||
{default = exportedPackages.${defaultPackageName};}
|
||||
// (lib.mapAttrs
|
||||
(name: version: {
|
||||
"${version}" = buildRacketWithPackages name;
|
||||
})
|
||||
args.packages);
|
||||
|
||||
allPackageSourceAttrs = l.pipe packageVersions [
|
||||
(l.mapAttrsToList (name: versions: (l.map
|
||||
(ver: let
|
||||
src = getSource name ver;
|
||||
srcDir = src.original or src;
|
||||
in
|
||||
l.nameValuePair name srcDir)
|
||||
versions)))
|
||||
l.flatten
|
||||
l.listToAttrs
|
||||
];
|
||||
|
||||
# Many of the details mimic https://github.com/Warbo/nix-helpers,
|
||||
# the license of which permits copying as long as we don't try to
|
||||
# patent anything.
|
||||
buildRacketWithPackages = name:
|
||||
produceDerivation "racket-with-${name}-env"
|
||||
(pkgs.runCommandCC "racket-with-${name}-env"
|
||||
{
|
||||
inherit (pkgs) racket;
|
||||
buildInputs = with pkgs; [makeWrapper racket];
|
||||
} ''
|
||||
${l.toShellVars {"allDepSrcs" = allPackageSourceAttrs;}}
|
||||
|
||||
mkdir -p $TMP/unpack
|
||||
|
||||
for p in ''${!allDepSrcs[@]}
|
||||
do
|
||||
mkdir $TMP/unpack/$p
|
||||
cp -R ''${allDepSrcs[$p]}/. $TMP/unpack/$p
|
||||
done
|
||||
|
||||
export PLTCONFIGDIR=$out/etc
|
||||
mkdir -p $PLTCONFIGDIR
|
||||
|
||||
cp $racket/etc/racket/config.rktd $PLTCONFIGDIR
|
||||
|
||||
$racket/bin/racket -t ${./make-new-config.rkt}
|
||||
|
||||
export TMP_RACO_HOME=$out/tmp-raco-home
|
||||
mkdir -p $TMP_RACO_HOME
|
||||
|
||||
chmod +w -R $TMP/unpack
|
||||
HOME=$TMP_RACO_HOME $racket/bin/raco pkg install --copy $(ls -d $TMP/unpack/*/)
|
||||
|
||||
for SUBPATH in $(ls -d $TMP_RACO_HOME/.local/share/racket/*/); # there is only one SUBPATH (whose name is the version number of Racket)
|
||||
do
|
||||
cp -r -t $PLTCONFIGDIR $SUBPATH/*
|
||||
done
|
||||
|
||||
rm -rf $TMP_RACO_HOME
|
||||
|
||||
mkdir -p $out/bin
|
||||
for EXE in $racket/bin/* $out/etc/bin/*;
|
||||
do
|
||||
NAME=$(basename "$EXE")
|
||||
makeWrapper "$EXE" "$out/bin/$NAME" --set PLTCONFIGDIR "$PLTCONFIGDIR"
|
||||
done
|
||||
'');
|
||||
in {
|
||||
packages = exportedPackages;
|
||||
};
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
#lang racket
|
||||
|
||||
(require setup/dirs)
|
||||
|
||||
(let* ([config-rktd-path (cleanse-path (build-path (find-config-dir) "config.rktd"))]
|
||||
[old-config-ht (with-input-from-file config-rktd-path read)]
|
||||
[property-alist '((bin-search-dirs . "bin/")
|
||||
(collects-search-dirs . "collects/")
|
||||
(doc-search-dirs . "doc/")
|
||||
(include-search-dirs . "include/")
|
||||
(lib-search-dirs . "lib/")
|
||||
(links-search-files . "links.rktd")
|
||||
(man-search-dirs . "man/")
|
||||
(pkgs-search-dirs . "pkgs/")
|
||||
(share-search-dirs . "share/"))]
|
||||
[make-path-string (lambda (subpath)
|
||||
(path->string (cleanse-path (build-path (find-config-dir) subpath))))]
|
||||
[final-config-ht (foldl (match-lambda**
|
||||
[((cons key subpath) accum)
|
||||
(hash-update accum
|
||||
key
|
||||
(curry cons (make-path-string subpath))
|
||||
'(#f))])
|
||||
old-config-ht
|
||||
property-alist)])
|
||||
(with-output-to-file config-rktd-path
|
||||
(lambda () (write final-config-ht))
|
||||
#:exists 'replace))
|
37
src/subsystems/racket/discoverers/default/default.nix
Normal file
37
src/subsystems/racket/discoverers/default/default.nix
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
dlib,
|
||||
lib,
|
||||
subsystem,
|
||||
...
|
||||
}: let
|
||||
l = lib // builtins;
|
||||
|
||||
discover = {
|
||||
tree,
|
||||
topLevel ? true,
|
||||
}:
|
||||
if (tree ? files."info.rkt")
|
||||
then [
|
||||
(dlib.construct.discoveredProject {
|
||||
inherit subsystem;
|
||||
relPath = tree.relPath;
|
||||
name =
|
||||
if topLevel
|
||||
then "main"
|
||||
else
|
||||
l.unsafeDiscardStringContext
|
||||
(l.last
|
||||
(l.splitString "/" (l.removeSuffix "/" "${tree.fullPath}")));
|
||||
translators = ["racket-impure"];
|
||||
subsystemInfo = {};
|
||||
})
|
||||
]
|
||||
else
|
||||
l.flatten (l.mapAttrsToList (_dirName: dirTree:
|
||||
discover {
|
||||
tree = dirTree;
|
||||
topLevel = false;
|
||||
}) (tree.directories or {}));
|
||||
in {
|
||||
inherit discover;
|
||||
}
|
80
src/subsystems/racket/translators/racket-impure/default.nix
Normal file
80
src/subsystems/racket/translators/racket-impure/default.nix
Normal file
@ -0,0 +1,80 @@
|
||||
{
|
||||
dlib,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
l = lib // builtins;
|
||||
in {
|
||||
type = "impure";
|
||||
|
||||
# A derivation which outputs a single executable at `$out`.
|
||||
# The executable will be called by dream2nix for translation
|
||||
# The input format is specified in /specifications/translator-call-example.json.
|
||||
# The first arg `$1` will be a json file containing the input parameters
|
||||
# like defined in /src/specifications/translator-call-example.json and the
|
||||
# additional arguments required according to extraArgs
|
||||
#
|
||||
# The program is expected to create a file at the location specified
|
||||
# by the input parameter `outFile`.
|
||||
# The output file must contain the dream lock data encoded as json.
|
||||
# See /src/specifications/dream-lock-example.json
|
||||
translateBin = {
|
||||
# dream2nix utils
|
||||
utils,
|
||||
# nixpkgs dependencies
|
||||
bash,
|
||||
coreutils,
|
||||
fetchurl,
|
||||
jq,
|
||||
nix,
|
||||
racket,
|
||||
runCommandLocal,
|
||||
writeScriptBin,
|
||||
...
|
||||
}: let
|
||||
pruned-racket-catalog = let
|
||||
src = fetchurl {
|
||||
url = "https://github.com/nix-community/pruned-racket-catalog/tarball/9f11e5ea5765c8a732c5e3129ca2b71237ae2bac";
|
||||
sha256 = "sha256-/n30lailqSndoqPGWcFquCpQWVQcciMiypXYLhNmFUo=";
|
||||
};
|
||||
in
|
||||
runCommandLocal "pruned-racket-catalog" {} ''
|
||||
mkdir $out
|
||||
cd $out
|
||||
tar --strip-components 1 -xf ${src}
|
||||
'';
|
||||
in
|
||||
utils.writePureShellScript
|
||||
[
|
||||
bash
|
||||
coreutils
|
||||
jq
|
||||
nix
|
||||
racket
|
||||
]
|
||||
''
|
||||
# according to the spec, the translator reads the input from a json file
|
||||
jsonInput=$1
|
||||
|
||||
# read the json input
|
||||
outputFile=$(realpath -m $(jq '.outputFile' -c -r $jsonInput))
|
||||
source=$(jq '.source' -c -r $jsonInput)
|
||||
relPath=$(jq '.project.relPath' -c -r $jsonInput)
|
||||
name=$(jq '.project.name' -c -r $jsonInput)
|
||||
|
||||
export RACKET_OUTPUT_FILE=$outputFile
|
||||
export RACKET_SOURCE=$source
|
||||
export RACKET_RELPATH=$relPath
|
||||
export RACKET_PKG_MAYBE_NAME=$name
|
||||
|
||||
racket -e '(require (file "${./generate-dream-lock.rkt}")) (generate-dream-lock "${pruned-racket-catalog}/pkgs-all")'
|
||||
'';
|
||||
|
||||
# If the translator requires additional arguments, specify them here.
|
||||
# When users run the CLI, they will be asked to specify these arguments.
|
||||
# There are only two types of arguments:
|
||||
# - string argument (type = "argument")
|
||||
# - boolean flag (type = "flag")
|
||||
# String arguments contain a default value and examples. Flags do not.
|
||||
extraArgs = {};
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
#lang racket/base
|
||||
|
||||
(require json)
|
||||
(require racket/file)
|
||||
(require racket/match)
|
||||
(require racket/list)
|
||||
(require racket/set)
|
||||
(require racket/string)
|
||||
(require setup/getinfo)
|
||||
|
||||
(provide generate-dream-lock)
|
||||
|
||||
;; XXX: We presently end up doing multiple DFSes in the course of
|
||||
;; generating multiple dream-lock.json files for foo, foo-lib,
|
||||
;; foo-test: the issue is that generating the foo dream-lock requires
|
||||
;; that we traverse foo-lib, and that subsequently generating the
|
||||
;; foo-lib dream-lock requires repeating the same traversal of foo-lib. How can this be avoided?
|
||||
|
||||
(define (generate-dream-lock pkgs-all-path)
|
||||
(letrec ([src-path (getenv "RACKET_SOURCE")]
|
||||
[rel-path (getenv "RACKET_RELPATH")]
|
||||
[package-path (simplify-path (cleanse-path (build-path src-path (if (string=? rel-path "")
|
||||
'same
|
||||
rel-path))))]
|
||||
[parent-path (simplify-path (cleanse-path (build-path package-path 'up)))]
|
||||
[package-name (if (string=? rel-path "")
|
||||
(getenv "RACKET_PKG_MAYBE_NAME")
|
||||
(path->string
|
||||
(match/values (split-path package-path)
|
||||
((_base subdir _must-be-dir?) subdir))))]
|
||||
[pkgs-all (with-input-from-file pkgs-all-path read)]
|
||||
[pkg-in-stdlib? (lambda (pkg-name)
|
||||
(or ;; Some people add racket itself as a dependency for some reason
|
||||
(string=? pkg-name "racket")
|
||||
(ormap (lambda (tag)
|
||||
;; XXX: would prefer to use memq, but tag is mutable for some reason
|
||||
(member tag '("main-distribution" "main-tests")))
|
||||
(hash-ref (hash-ref pkgs-all pkg-name) 'tags))))]
|
||||
[dep-alist-from-catalog (hash-map pkgs-all
|
||||
(match-lambda**
|
||||
[(name (hash-table ('dependencies dependencies)))
|
||||
(let ([external-deps (filter-not pkg-in-stdlib? dependencies)])
|
||||
(cons name external-deps))]))]
|
||||
[compute-overridden-dep-lists
|
||||
(lambda (name dir)
|
||||
(let ([info-procedure (get-info/full dir)])
|
||||
(and info-procedure
|
||||
(cons name
|
||||
(remove-duplicates
|
||||
(filter-not pkg-in-stdlib?
|
||||
(map (match-lambda
|
||||
[(or (cons pkg-name _) pkg-name)
|
||||
pkg-name])
|
||||
(append (with-handlers ([exn:fail? (lambda (_) '())])
|
||||
(info-procedure 'deps))
|
||||
(with-handlers ([exn:fail? (lambda (_) '())])
|
||||
(info-procedure 'build-deps))))))))))]
|
||||
[dep-list-overrides
|
||||
;; XXX: this probably doesn't capture every case since
|
||||
;; Racket doesn't seem to enforce much structure in a
|
||||
;; multi-package repo, but it accounts for the only cases
|
||||
;; that a sane person would choose
|
||||
(if (string=? rel-path "")
|
||||
(list (compute-overridden-dep-lists package-name package-path))
|
||||
(let* ([sibling-paths (filter directory-exists?
|
||||
(directory-list parent-path #:build? #t))]
|
||||
[names-of-sibling-paths (map (lambda (p)
|
||||
;; XXX: maybe not very DRY
|
||||
(path->string
|
||||
(match/values (split-path p)
|
||||
((_base dir-fragment _must-be-dir?) dir-fragment))))
|
||||
sibling-paths)])
|
||||
(filter-map compute-overridden-dep-lists
|
||||
names-of-sibling-paths
|
||||
sibling-paths)))]
|
||||
[names-of-overridden-packages (apply set (map car dep-list-overrides))]
|
||||
[graph (make-immutable-hash (append dep-alist-from-catalog
|
||||
dep-list-overrides))]
|
||||
;; TODO: no effort is made to handle cycles right now
|
||||
[dfs (lambda (u dependency-subgraph)
|
||||
(if (hash-has-key? dependency-subgraph u)
|
||||
dependency-subgraph
|
||||
(let ([destinations (hash-ref graph u)])
|
||||
(foldl dfs
|
||||
(hash-set dependency-subgraph u destinations)
|
||||
destinations))))]
|
||||
[dependency-subgraph (dfs package-name (make-immutable-hash))]
|
||||
[generic (make-immutable-hash
|
||||
`((subsystem . "racket")
|
||||
(location . ,rel-path)
|
||||
(sourcesAggregatedHash . ,(json-null))
|
||||
(defaultPackage . ,package-name)
|
||||
(packages . ,(make-immutable-hash `((,(string->symbol package-name) . "0.0.0"))))))]
|
||||
[sources-from-catalog
|
||||
(hash-map pkgs-all
|
||||
(match-lambda**
|
||||
[(name (hash-table
|
||||
('versions
|
||||
(hash-table
|
||||
('default
|
||||
(hash-table
|
||||
('source_url url)))))
|
||||
('checksum rev)))
|
||||
(let* ([source-with-removed-http-or-git-double-slash (regexp-replace #rx"^(?:git|http)://" url "https://")]
|
||||
[left-trimmed-source (string-trim source-with-removed-http-or-git-double-slash "git+" #:right? #f)]
|
||||
[maybe-match-path (regexp-match #rx"\\?path=([^#]+)" left-trimmed-source)]
|
||||
[trimmed-source (regexp-replace #rx"(?:/tree/.+)?(?:\\?path=.+)?$" left-trimmed-source "")])
|
||||
(cons (string->symbol name)
|
||||
(make-immutable-hash
|
||||
`((0.0.0 . ,(make-immutable-hash
|
||||
(append (match maybe-match-path
|
||||
[(list _match dir)
|
||||
`((dir . ,(regexp-replace* #rx"%2F" dir "/")))]
|
||||
[_ '()])
|
||||
`((url . ,trimmed-source)
|
||||
(rev . ,rev)
|
||||
(type . "git")
|
||||
;; TODO: sha256?
|
||||
))))))))]))]
|
||||
[sources-from-repo (if (string=? rel-path "")
|
||||
(list (cons (string->symbol package-name)
|
||||
(make-immutable-hash
|
||||
`((0.0.0 . ,(make-immutable-hash
|
||||
`((type . "path")
|
||||
(path . ,src-path))))))))
|
||||
(set-map names-of-overridden-packages
|
||||
(lambda (name)
|
||||
(cons (string->symbol name)
|
||||
(make-immutable-hash
|
||||
`((0.0.0 . ,(make-immutable-hash
|
||||
`((type . "path")
|
||||
(path . ,(path->string (build-path parent-path (string-append-immutable name "/")))))))))))))]
|
||||
[sources-hash-table (make-immutable-hash (append sources-from-catalog
|
||||
sources-from-repo))]
|
||||
[sources (make-immutable-hash (hash-map dependency-subgraph
|
||||
(lambda (name _v)
|
||||
(cons (string->symbol name) (hash-ref sources-hash-table (string->symbol name))))))]
|
||||
[dream-lock (make-immutable-hash
|
||||
`((_generic . ,generic)
|
||||
(sources . ,sources)
|
||||
(_subsystem . ,(make-immutable-hash))
|
||||
(dependencies . ,(make-immutable-hash
|
||||
(hash-map dependency-subgraph
|
||||
(lambda (name dep-list)
|
||||
(cons (string->symbol name)
|
||||
(make-immutable-hash `((0.0.0 . ,(map (lambda (dep-name) (list dep-name "0.0.0")) dep-list)))))))))))])
|
||||
(make-parent-directory* (getenv "RACKET_OUTPUT_FILE"))
|
||||
(with-output-to-file (getenv "RACKET_OUTPUT_FILE")
|
||||
(lambda () (write-json dream-lock))
|
||||
#:exists 'replace)))
|
Loading…
Reference in New Issue
Block a user