Files
guix-tribes/tribes/packages/source.scm

471 lines
18 KiB
Scheme

(define-module (tribes packages source)
#:use-module ((guix licenses) #:prefix license:)
#:use-module (guix download)
#:use-module (guix git-download)
#:use-module (guix gexp)
#:use-module (guix packages)
#:use-module (guix utils)
#:use-module (gnu packages autotools)
#:use-module (gnu packages base)
#:use-module (gnu packages commencement)
#:use-module (gnu packages gawk)
#:use-module (gnu packages linux)
#:use-module (gnu packages m4)
#:use-module (gnu packages node)
#:use-module (gnu packages perl)
#:use-module (gnu packages pkg-config)
#:use-module ((tribes packages mix) #:prefix mix:)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-13)
#:export (fetch-mix-deps
fetch-npm-deps
local-tribes-package
tribes-package
tribes-source-package
tribes-source-directory->local-file))
;; Recursive sha256 of the raw deps tree produced by `mix deps.get --only prod`
;; from the current Tribes mix.lock, with git metadata stripped except for
;; .git/HEAD in SCM dependencies.
(define %tribes-raw-mix-deps-sha256
"0mv4jva8zkx8cq1b84hn65bl913nnhkvf25g6fi93z3jm35jy0pc")
;; Recursive sha256 of the Tribes-specific prepared deps tree, after injecting
;; the upstream secp256k1 source into the Hex package and patching its build
;; recipe to avoid build-time network access.
(define %tribes-mix-deps-sha256
"0ksjnc9gnjijp1nbz3jlvl9kz8w7hx1a0ssms1dvd15rr25gn0d4")
;; Recursive sha256 of assets/node_modules generated from assets/package-lock.json
;; in an isolated build environment, with local file dependencies resolved from
;; the vendored Mix dependency tree.
(define %tribes-npm-deps-sha256
"1bfzs67ffhwcm0dwdkb1jqnbn3fpgj22zfhd2y907w8daj62gahv")
(define %tribes-home-page
"https://git.teralink.net/tribes/tribes.git")
(define %tribes-commit
"1e026a60fdabcec8b9ec0a850a03f903d02496e7")
(define %tribes-revision "1")
(define %tribes-version
(git-version "0.2.0" %tribes-revision %tribes-commit))
(define %tribes-source-sha256
"1lqls1ngy3vpf0hh9jn44h1g9rx1hglj9ik3zi1ywcr4q01pmv68")
(define %tribes-upstream-source
(origin
(method git-fetch)
(uri (git-reference
(url %tribes-home-page)
(commit %tribes-commit)))
(file-name (git-file-name "tribes" %tribes-version))
(sha256
(base32 %tribes-source-sha256))))
(define %libsecp256k1-upstream-source
(origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/bitcoin-core/secp256k1")
(commit "v0.7.1")))
(file-name (git-file-name "secp256k1" "0.7.1"))
(sha256
(base32 "10cvh8jks3rjg6p7y0vm1v4kw9y7vljbfijj0zxwkxzysxx60w0f"))))
(define %heroicons-upstream-source
(origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/tailwindlabs/heroicons")
(commit "0435d4ca364a608cc75e2f8683d374e55abbae26")))
(file-name (git-file-name "heroicons" "2.2.0"))
(sha256
(base32 "15di4p755ydivkbv9mv9hb8lsdgzb5zq77ljdnyp76cvykanpk15"))))
(define %excluded-root-basenames
'(".cache"
".claude"
".direnv"
".elixir_ls"
".git"
".hex"
".home"
".mypy_cache"
".mix-home"
".pre-commit-config.yaml"
"_build"
"deps"
"erl_crash.dump"
"node_modules"
"result"))
(define (root-artifact-path? root file entry)
(let ((entry-path (string-append root "/" entry)))
(or (string=? file entry-path)
(string-prefix? (string-append entry-path "/") file))))
(define (transient-source-file? root file)
(let ((base (basename file)))
(or (any (lambda (entry)
(root-artifact-path? root file entry))
%excluded-root-basenames)
(root-artifact-path? root file ".env")
(string-prefix? ".devenv" base)
(string=? base ".env")
(string-suffix? ".log" base)
(string-suffix? ".tsbuildinfo" base)
(string-suffix? ".db" base)
(string-suffix? ".db-shm" base)
(string-suffix? ".db-wal" base))))
(define (tribes-source-select? root file stat)
(or (string=? file root)
(not (transient-source-file? root file))))
(define (tribes-source-directory->local-file directory)
"Return DIRECTORY as a recursively copied local-file, excluding transient
build artifacts and, when available, anything not tracked in the enclosing Git
checkout."
(let ((git-select? (git-predicate directory)))
(local-file directory
#:recursive? #t
#:select?
(if git-select?
(lambda (file stat)
(and (git-select? file stat)
(tribes-source-select? directory file stat)))
(lambda (file stat)
(tribes-source-select? directory file stat))))))
(define fetch-mix-deps mix:fetch-mix-deps)
(define* (fetch-npm-deps source
#:key
mix-fod-deps
(name "tribes-npm-deps")
(version "0.2.0")
(sha256 %tribes-npm-deps-sha256))
"Return a fixed-output node_modules tree for SOURCE/assets/package-lock.json,
with local file dependencies resolved from MIX-FOD-DEPS."
(computed-file
(string-append name "-" version)
(with-imported-modules '((guix build utils))
#~(begin
(use-modules (guix build utils))
(define out #$output)
(define work (string-append (getcwd) "/build"))
(define app-dir (string-append work "/app"))
(define deps-dir (string-append app-dir "/deps"))
(define assets-dir (string-append app-dir "/assets"))
(define certs-dir
#$(file-append nss-certs "/etc/ssl/certs"))
(define cert-file
(string-append work "/ca-certificates.crt"))
(define path
(string-join
(list #$(file-append node "/bin")
#$(file-append bash-minimal "/bin")
#$(file-append coreutils "/bin")
#$(file-append findutils "/bin")
#$(file-append git-minimal "/bin")
#$(file-append gzip "/bin")
#$(file-append tar "/bin")
(or (getenv "PATH") ""))
":"))
(mkdir-p work)
(copy-recursively #+source app-dir #:follow-symlinks? #t)
(invoke #$(file-append coreutils "/bin/chmod") "-R" "u+w" app-dir)
(when (file-exists? deps-dir)
(delete-file-recursively deps-dir))
(copy-recursively #+mix-fod-deps deps-dir #:follow-symlinks? #t)
(invoke #$(file-append coreutils "/bin/chmod") "-R" "u+w" deps-dir)
(invoke #$(file-append bash-minimal "/bin/sh")
"-c"
(string-append
#$(file-append coreutils "/bin/cat")
" "
certs-dir
"/*.pem > "
cert-file))
(setenv "PATH" path)
(setenv "HOME" (string-append work "/home"))
(setenv "XDG_CACHE_HOME" (string-append work "/cache"))
(setenv "npm_config_cache" (string-append work "/npm-cache"))
(setenv "npm_config_userconfig" (string-append work "/npmrc"))
(setenv "SSL_CERT_DIR" certs-dir)
(setenv "SSL_CERT_FILE" cert-file)
(setenv "NODE_ENV" "production")
(mkdir-p (getenv "HOME"))
(mkdir-p (getenv "XDG_CACHE_HOME"))
(mkdir-p (getenv "npm_config_cache"))
(with-directory-excursion assets-dir
(invoke "npm" "ci" "--ignore-scripts" "--no-audit" "--no-fund"))
(mkdir-p out)
(copy-recursively (string-append assets-dir "/node_modules")
out
#:follow-symlinks? #t)))
#:options
`(#:hash ,(base32 sha256)
#:hash-algo sha256
#:recursive? #t
#:leaked-env-vars ("http_proxy" "https_proxy"
"LC_ALL" "LC_MESSAGES" "LANG" "COLUMNS"))))
(define* (tribes-mix-deps source
#:key
(name "tribes-mix-deps")
(version "0.2.0")
(sha256 %tribes-mix-deps-sha256)
(raw-sha256 %tribes-raw-mix-deps-sha256)
(mix-env "prod")
(mix-target "host"))
"Return the Tribes Mix dependency tree, prepared from the raw lockfile
resolution by injecting extra pre-fetched sources needed for offline builds."
(let ((raw-mix-deps
(fetch-mix-deps source
#:name (string-append name "-raw")
#:version version
#:sha256 raw-sha256
#:mix-env mix-env
#:mix-target mix-target)))
(computed-file
(string-append name "-" version)
(with-imported-modules '((guix build utils))
#~(begin
(use-modules (guix build utils))
(define out #$output)
(define deps-dir (string-append (getcwd) "/deps"))
(copy-recursively #+raw-mix-deps deps-dir #:follow-symlinks? #t)
(invoke #$(file-append coreutils "/bin/chmod") "-R" "u+w" deps-dir)
(let* ((libsecp-dir (string-append deps-dir "/lib_secp256k1"))
(libsecp-src-dir (string-append libsecp-dir "/c_src/secp256k1"))
(libsecp-makefile (string-append libsecp-dir "/Makefile"))
(libsecp-fetched-stamp (string-append libsecp-src-dir "/.fetched")))
(when (file-exists? libsecp-dir)
(mkdir-p (string-append libsecp-dir "/c_src"))
(when (file-exists? libsecp-src-dir)
(delete-file-recursively libsecp-src-dir))
(copy-recursively #+%libsecp256k1-upstream-source
libsecp-src-dir
#:follow-symlinks? #t)
(invoke #$(file-append coreutils "/bin/chmod")
"-R" "u+w"
libsecp-dir)
(when (file-exists? libsecp-makefile)
;; Avoid depending on preserved executable bits or /bin/sh in the
;; generated Autoconf script.
(substitute* libsecp-makefile
(("\\./autogen\\.sh") "sh ./autogen.sh")
(("\\./configure") "sh ./configure")))
(call-with-output-file libsecp-fetched-stamp
(lambda (_port) #t))))
(mkdir-p out)
(copy-recursively deps-dir out #:follow-symlinks? #t)))
#:options
`(#:hash ,(base32 sha256)
#:hash-algo sha256
#:recursive? #t
#:leaked-env-vars ("http_proxy" "https_proxy"
"LC_ALL" "LC_MESSAGES" "LANG" "COLUMNS")))))
(define* (tribes-source-package source
#:key
(mix-deps #f)
(mix-deps-sha256 %tribes-mix-deps-sha256)
(raw-mix-deps-sha256
%tribes-raw-mix-deps-sha256)
(npm-deps #f)
(npm-deps-sha256 %tribes-npm-deps-sha256)
(name "tribes")
(version %tribes-version)
(home-page %tribes-home-page)
(synopsis "Tribes social app")
(description
"Tribes social application built from source as a
production Elixir release using vendored Mix and npm dependency trees."))
"Return a Guix package that builds a production Tribes release from SOURCE,
using MIX-DEPS and NPM-DEPS as pre-fetched dependency trees resolved from
mix.lock and assets/package-lock.json."
(let* ((mix-deps-source
(or mix-deps
(tribes-mix-deps source
#:name (string-append name "-mix-deps")
#:version version
#:sha256 mix-deps-sha256
#:raw-sha256 raw-mix-deps-sha256)))
(npm-deps-source
(or npm-deps
(fetch-npm-deps source
#:mix-fod-deps mix-deps-source
#:name (string-append name "-npm-deps")
#:version version
#:sha256 npm-deps-sha256))))
(mix:mix-release-package
source
#:mix-fod-deps mix-deps-source
#:name name
#:version version
#:home-page home-page
#:synopsis synopsis
#:description description
#:license license:asl2.0
#:native-inputs
(list autoconf
autoconf-wrapper
automake
gcc-toolchain
gawk
grep
gnu-make
libtool
linux-libre-headers
m4
node
perl
pkg-config
sed)
#:path-inputs
(list autoconf
autoconf-wrapper
automake
gcc-toolchain
gawk
grep
gnu-make
libtool
m4
node
perl
pkg-config
sed)
#:aclocal-inputs
(list automake libtool)
#:setup-gexp
#~(begin
(define kernel-headers-dir
#$(file-append linux-libre-headers "/include"))
(let ((existing-cpath (getenv "CPATH")))
(setenv "CPATH"
(if existing-cpath
(string-append kernel-headers-dir ":" existing-cpath)
kernel-headers-dir)))
(let ((existing-c-include-path (getenv "C_INCLUDE_PATH")))
(setenv "C_INCLUDE_PATH"
(if existing-c-include-path
(string-append kernel-headers-dir ":"
existing-c-include-path)
kernel-headers-dir)))
(setenv "CC" #$(file-append gcc-toolchain "/bin/gcc"))
(setenv "CXX" #$(file-append gcc-toolchain "/bin/g++"))
(setenv "CPP"
(string-append #$(file-append gcc-toolchain "/bin/gcc")
" -E")))
#:configure-gexp
#~(begin
;; Absinthe has been the last fragile dependency in Guix builds so far.
;; Compile its immediate deps first, then compile Absinthe in its own
;; pass before compiling the remaining dependency graph.
(invoke "mix" "deps.compile" "nimble_parsec" "telemetry" "decimal")
(let ((existing-erl-flags (getenv "ERL_FLAGS")))
;; Keep the scheduler count pinned while compiling Absinthe so
;; toolchain-sensitive ordering issues stay deterministic.
(setenv "ERL_FLAGS"
(if existing-erl-flags
(string-append existing-erl-flags " +S 1:1")
"+S 1:1"))
(invoke "mix" "deps.compile" "absinthe" "--force")
(if existing-erl-flags
(setenv "ERL_FLAGS" existing-erl-flags)
(unsetenv "ERL_FLAGS")))
(invoke "mix" "deps.compile"))
#:build-gexp
#~(begin
(invoke "mix" "compile" "--no-deps-check")
(let ((assets-node-modules "assets/node_modules"))
(when (file-exists? assets-node-modules)
(delete-file-recursively assets-node-modules))
(copy-recursively #+npm-deps-source
assets-node-modules
#:follow-symlinks? #t)
(invoke #$(file-append coreutils "/bin/chmod")
"-R"
"u+w"
assets-node-modules)
(invoke "find"
assets-node-modules
"-type" "f"
"-path" "*/@esbuild/*/bin/esbuild"
"-exec" "chmod" "+x" "{}" "+")
(invoke "find"
assets-node-modules
"-type" "f"
"-path" "*/.bin/*"
"-exec" "chmod" "+x" "{}" "+"))
(let ((heroicons-dir "deps/heroicons"))
(when (file-exists? heroicons-dir)
(delete-file-recursively heroicons-dir))
(copy-recursively #+%heroicons-upstream-source
heroicons-dir
#:follow-symlinks? #t)
(invoke #$(file-append coreutils "/bin/chmod")
"-R"
"u+w"
heroicons-dir))
(setenv "NODE_PATH"
(string-append (getcwd) "/deps:"
(getcwd) "/_build/prod"))
(with-directory-excursion "assets"
(invoke "npm" "run" "build.css" "--" "--minify")
(invoke "npm" "run" "build.js" "--" "--minify"))
(invoke "mix" "phx.digest"))
#:install-gexp
#~(begin
(invoke "mix" "release" "--no-deps-check" "--path" out)
(let ((launcher (string-append out "/bin/" #$name))
(launcher-app (string-append out "/bin/" #$name "-app")))
(when (file-exists? launcher)
(rename-file launcher launcher-app)))))))
(define* (local-tribes-package directory
#:key
(version "dev")
(mix-deps-sha256 #f)
(raw-mix-deps-sha256 #f)
(npm-deps-sha256 #f))
"Return a Tribes package built from a local source checkout. Hash overrides
allow development against changed lockfiles without changing the canonical
package pin."
(tribes-source-package
(tribes-source-directory->local-file directory)
#:version version
#:mix-deps-sha256 (or mix-deps-sha256 %tribes-mix-deps-sha256)
#:raw-mix-deps-sha256
(or raw-mix-deps-sha256 %tribes-raw-mix-deps-sha256)
#:npm-deps-sha256
(or npm-deps-sha256 %tribes-npm-deps-sha256)))
(define tribes-package
(tribes-source-package %tribes-upstream-source
#:version %tribes-version))