Project import generated by Copybara.

GitOrigin-RevId: e6e19f3d81a982a62e1bba08f0b4f7fdc21b4ea0
This commit is contained in:
Default email 2022-05-19 16:39:52 +02:00
parent ac06d71d66
commit a291c8690a
8182 changed files with 116586 additions and 1602792 deletions

View file

@ -1,5 +1,7 @@
# Configure the local PATH to contain tools which are fetched ad-hoc # Configure the local PATH to contain tools which are fetched ad-hoc
# from Nix. # from Nix.
export PATH="${PWD}/bin:${PATH}" out=$(nix-build -A tools.depot-deps --no-out-link)
export REPO_ROOT="${PWD}" PATH_add "$out/bin"
watch_file tools/depot-deps.nix

View file

@ -21,3 +21,6 @@ c758de9d22506eb279c5abe61f621e5c8f61af95
# style(3p/nix): Final act in the brace-wrapping saga # style(3p/nix): Final act in the brace-wrapping saga
39087321811e81e26a1a47d6967df1088dcf0e95 39087321811e81e26a1a47d6967df1088dcf0e95
# style: format entire depot with nixpkgs-fmt
aa122cbae78ce97d60c0c98ba14df753d97e40b1

13
third_party/tvl/.nixery/README.md vendored Normal file
View file

@ -0,0 +1,13 @@
Nixery set
==========
This folder exports a special import of the depot Nix structure that is
compatible with Nixery, by extending nixpkgs with a `tvl` attribute containing
the depot.
This is required because Nixery expects its package set to look like nixpkgs at
the top-level.
In the future we might want to patch Nixery to not require this (e.g. make it
possible to pass `third_party.nixpkgs` as a key at which to find the nixpkgs
structure).

6
third_party/tvl/.nixery/default.nix vendored Normal file
View file

@ -0,0 +1,6 @@
# See README.md
{ depot ? import ../. {}, ... }:
depot.third_party.nixpkgs.extend(_: _: {
tvl = depot;
})

View file

@ -1,22 +0,0 @@
# This configuration file changes some defaults from the ones
# documented on https://rust-lang.github.io/rustfmt/
#
# All other settings are left at the defaults.
edition = "2018"
newline_style = "Unix"
use_try_shorthand = true
# Unstable settings that we want in the future, once they are
# available:
#
# combine_control_expr = false
# comment_width = 100
# condense_wildcard_suffixes = true
# format_code_in_doc_comments = true
# inline_attribute_width = 100
# match_block_trailing_comma = true
# merge_imports = true
# normalize_comments = true
# overflow_delimited_expr = true
# wrap_comments = true

View file

@ -1,6 +1,7 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2019 Vincent Ambo Copyright (c) 2019 Vincent Ambo
Copyright (c) 2020-2021 The TVL Authors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -2,3 +2,5 @@ inherited: false
owners: owners:
- tazjin - tazjin
- lukegb - lukegb
- grfn
- sterni

View file

@ -1,19 +1,16 @@
depot depot
===== =====
[![Build status](https://badge.buildkite.com/016bff4b8ae2704a3bbbb0a250784e6692007c582983b6dea7.svg?branch=canon)](https://buildkite.com/tvl/depot) [![Build status](https://badge.buildkite.com/016bff4b8ae2704a3bbbb0a250784e6692007c582983b6dea7.svg?branch=refs/heads/canon)](https://buildkite.com/tvl/depot)
This repository is the [monorepo][] for the community around [tazjin's virus This repository is the [monorepo][] for the community around [The
lounge][tvl], containing our personal tools and infrastructure. Everything in Virus Lounge][tvl], containing our personal tools and infrastructure.
here is built using [Nix][]. Everything in here is built using [Nix][].
A large portion of the software here is very self-referential, meaning that it A large portion of the software here is very self-referential, meaning that it
exists to sustain the operation of the repository. This is the case because we exists to sustain the operation of the repository. This is the case because we
partially see this as [an experiment][] in tooling for monorepos. partially see this as [an experiment][] in tooling for monorepos.
If you've ended up here and have no idea who I am, feel free to follow me [on
Twitter][].
# Highlights # Highlights
## Services ## Services
@ -41,15 +38,20 @@ Twitter][].
dynamically via dynamically via
[`//ops/pipelines`](https://cs.tvl.fyi/depot/-/tree/ops/pipelines). [`//ops/pipelines`](https://cs.tvl.fyi/depot/-/tree/ops/pipelines).
* A search service that makes TVL services available via textual
shortcuts is available: [atward](https://at.tvl.fyi)
All services that we host are deployed on NixOS machines that we manage. Their All services that we host are deployed on NixOS machines that we manage. Their
configuration is tracked in `//ops/nixos`. configuration is tracked in `//ops/{modules,machines}`.
## Nix ## Nix
* `//third_party/nix` contains Tvix, [our fork][tvix] of the Nix package manager
* [`//nix/readTree`](https://cs.tvl.fyi/depot/-/blob/nix/readTree/README.md) * [`//nix/readTree`](https://cs.tvl.fyi/depot/-/blob/nix/readTree/README.md)
contains the Nix code which automatically registers projects in our Nix contains the Nix code which automatically registers projects in our Nix
attribute hierarchy based on their in-tree location attribute hierarchy based on their in-tree location
* [`//tools/nixery`](https://cs.tvl.fyi/depot/-/tree/tools/nixery)
contains the source code of [Nixery][], a container registry that
can build images ad-hoc from Nix packages
* `//nix/yants` contains **Y**et **A**nother **N**ix **T**ype **S**ystem, which * `//nix/yants` contains **Y**et **A**nother **N**ix **T**ype **S**ystem, which
we use for a variety of things throughout the repository we use for a variety of things throughout the repository
* `//nix/buildGo` implements a Nix library that can build Go software in the * `//nix/buildGo` implements a Nix library that can build Go software in the
@ -58,6 +60,10 @@ configuration is tracked in `//ops/nixos`.
* `//nix/buildLisp` implements a Nix library that can build Common Lisp * `//nix/buildLisp` implements a Nix library that can build Common Lisp
software. Currently only SBCL is supported. Lisp programs in this repository software. Currently only SBCL is supported. Lisp programs in this repository
are built using this library. are built using this library.
* `//web/bubblegum` contains a CGI-based web framework written in Nix.
* `//nix/nint`: A shebang-compatible interpreter wrapper for Nix.
* `//tvix` contains initial work towards a modular architecture for Nix.
* `//third_party/nix` contains [our fork][tvix] of the Nix package manager.
We have a variety of other tools and libraries in the `//nix` folder which may We have a variety of other tools and libraries in the `//nix` folder which may
be of interest. be of interest.
@ -84,9 +90,9 @@ Some examples:
* `//users/tazjin/homepage` && `//users/tazjin/blog`: A Nix-based static site * `//users/tazjin/homepage` && `//users/tazjin/blog`: A Nix-based static site
generator which generates the web page and Atom feed for generator which generates the web page and Atom feed for
[tazj.in](https://tazj.in) [tazj.in](https://tazj.in)
* `//users/tazjin/finito`: A persistent finite-state machine library for Rust. * `//users/grfn/xanthous`: A (WIP) TUI RPG, written in Haskell.
* `//users/glittershark/xanthous`: A (WIP) TUI RPG, written in Haskell.
* `//users/tazjin/emacs`: tazjin's Emacs & EXWM configuration * `//users/tazjin/emacs`: tazjin's Emacs & EXWM configuration
* `//users/tazjin/finito`: A persistent finite-state machine library for Rust.
# Licensing # Licensing
@ -99,11 +105,21 @@ If you'd like to contribute to any of the tools in here, please check out the
[contribution guidelines](./docs/CONTRIBUTING.md) and our [code of [contribution guidelines](./docs/CONTRIBUTING.md) and our [code of
conduct](./docs/CODE_OF_CONDUCT.md). conduct](./docs/CODE_OF_CONDUCT.md).
IRC users can find us in [`#tvl`][tvl-irc] on [hackint][], which is also
reachable [via XMPP][hackint-xmpp] at [`#tvl@irc.hackint.org`][tvl-xmpp] (sic!).
Hackint also provide a [web chat][tvl-webchat].
[monorepo]: https://en.wikipedia.org/wiki/Monorepo [monorepo]: https://en.wikipedia.org/wiki/Monorepo
[tvl]: https://tvl.fyi [tvl]: https://tvl.fyi
[Nix]: https://nixos.org/nix [Nix]: https://nixos.org/nix
[an experiment]: https://tvl.fyi/monorepo-doc [an experiment]: https://tvl.fyi/monorepo-doc
[on Twitter]: https://twitter.com/tazjin
[panettone]: https://cs.tvl.fyi/depot@canon/-/tree/web/panettone [panettone]: https://cs.tvl.fyi/depot@canon/-/tree/web/panettone
[tvix]: https://cs.tvl.fyi/depot/-/blob/third_party/nix/README.md [tvix]: https://cs.tvl.fyi/depot/-/blob/third_party/nix/README.md
[dottime]: https://dotti.me [dottime]: https://dotti.me
[tvl-irc]: ircs://irc.hackint.org:6697/#tvl
[hackint]: https://hackint.org/
[hackint-xmpp]: https://hackint.org/transport/xmpp
[tvl-xmpp]: xmpp:#tvl@irc.hackint.org?join
[tvl-webchat]: https://webirc.hackint.org/#ircs://irc.hackint.org/#tvl
[Nixery]: https://nixery.dev

View file

@ -1 +1 @@
1. Only those who pay the Golden Price may bear The Wheel. 1. Only those who pay the Golden Price may bear The Wheel (for the most part).

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

View file

@ -1,70 +0,0 @@
#!/usr/bin/env bash
# This script dispatches invocations transparently to programs instantiated from
# Nix.
#
# To add a new tool, insert it into the case statement below by setting `attr`
# to the key in nixpkgs which represents the program you want to run.
set -ueo pipefail
readonly REPO_ROOT=$(dirname "$0")/..
TARGET_TOOL=$(basename "$0")
case "${TARGET_TOOL}" in
age)
attr="third_party.age"
;;
age-keygen)
attr="third_party.age"
;;
aoc2019)
attr="fun.aoc2019.${1}"
;;
depot-build)
attr="tools.depot-build"
;;
git-bug)
attr="third_party.git-bug"
;;
gerrit)
attr="tools.gerrit-cli"
;;
hash-password)
attr="tools.hash-password"
;;
kontemplate)
attr="kontemplate"
;;
meson)
attr="third_party.meson"
;;
ninja)
attr="third_party.ninja"
;;
perf-flamegraph)
attr="tools.perf-flamegraph"
;;
rebuild-system)
attr="ops.nixos.rebuild-system"
;;
rebuilder)
attr="users.tazjin.nixos.rebuilder"
;;
rink)
attr="third_party.rink"
;;
stern)
attr="third_party.stern"
;;
terraform)
attr="third_party.terraform-gcp"
;;
*)
echo "The tool '${TARGET_TOOL}' is currently not installed in this repository."
exit 1
;;
esac
result=$(nix-build --no-out-link --attr "${attr}" "${REPO_ROOT}")
PATH="${result}/bin:$PATH"
exec "${TARGET_TOOL}" "${@}"

4
third_party/tvl/corp/LICENSE vendored Normal file
View file

@ -0,0 +1,4 @@
Copyright 2021 ООО ТВЛ
Code under this folder may be redistributed as part of the TVL depot
repository. All other usage rights for this code are reserved.

3
third_party/tvl/corp/OWNERS vendored Normal file
View file

@ -0,0 +1,3 @@
inherited: false
owners:
- tvl-employees

26
third_party/tvl/corp/website/content.md vendored Normal file
View file

@ -0,0 +1,26 @@
The Virus Lounge
================
----------------
<img class="tvl-logo" src="https://static.tvl.su/latest/logo-animated.svg"
alt="Virus with lambda-shaped spike proteins sitting on an armchair">
Welcome to the corporate face of [The Virus Lounge][tvl-fyi].
We provide technology consulting around a variety of topics, for
example:
* Advice and setup of organisation-wide monorepos for effective
developer workflows, including associated tooling like CI/CD
* Assistance with anything related to Nix/NixOS
* Software development in various languages (Rust, Common Lisp,
Erlang, Java and more)
We might be able to help you with other things on request.
Note: We are still in the process of getting started and have limited
capacity at the moment. If you would like our help, please reach out
at **contact {at} tvl.su** for a discussion.
[tvl-fyi]: https://tvl.fyi

View file

@ -0,0 +1,37 @@
{ depot, pkgs, ... }:
let
# https://developers.google.com/search/docs/advanced/structured-data/logo
structuredData = {
"@context" = "https://schema.org";
"@type" = "Organisation";
url = "https://tvl.su";
logo = "https://static.tvl.fyi/${depot.web.static.drvHash}/logo-animated.svg";
};
index = depot.web.tvl.template {
title = "TVL (The Virus Lounge) - Software consulting";
content = builtins.readFile ./content.md;
extraFooter = "\n|\n © ООО ТВЛ";
# TODO(tazjin): The `.tvl-logo` thing can probably go in the shared CSS.
extraHead = ''
<meta name="description" content="TVL provides technology consulting for monorepos, Nix, and other SRE/DevOps/Software Engineering topics.">
<script type="application/ld+json">
${builtins.toJSON structuredData}
</script>
<style>
.tvl-logo {
width: 60%;
display: block;
margin-left: auto;
margin-right: auto;
}
</style>
'';
};
in
pkgs.runCommandNoCC "corp-website" { } ''
mkdir $out
cp ${index} $out/index.html
''

View file

@ -1,109 +1,122 @@
# This file sets up the top-level package set by traversing the package tree # This file sets up the top-level package set by traversing the package tree
# (see read-tree.nix for details) and constructing a matching attribute set # (see //nix/readTree for details) and constructing a matching attribute set
# tree. # tree.
#
# This makes packages accessible via the Nixery instance that is configured to
# use this repository as its nixpkgs source.
{ ... }@args: { nixpkgsBisectPath ? null
, parentTargetMap ? null
with builtins; , nixpkgsConfig ? { }
, ...
}@args:
let let
# This definition of fix is identical to <nixpkgs>.lib.fix, but the global inherit (builtins)
# package set is not available here. filter
fix = f: let x = f x; in x; ;
# Global configuration that all packages are called with. readTree = import ./nix/readTree { };
config = depot: {
inherit depot;
# Expose lib & ciBuilds attributes to packages. # Disallow access to //users from other depot parts.
inherit (depot) ciBuilds lib; usersFilter = readTree.restrictFolder {
folder = "users";
reason = ''
Code under //users is not considered stable or dependable in the
wider depot context. If a project under //users is required by
something else, please move it to a different depot path.
'';
# Pass third_party as 'pkgs' (for compatibility with external exceptions = [
# imports for certain subdirectories) # whitby is allowed to access //users for several reasons:
pkgs = depot.third_party; #
# 1. User SSH keys are set in //users.
# 2. Some personal websites or demo projects are served from it.
[ "ops" "machines" "whitby" ]
# Due to evaluation order this also affects these targets.
# TODO(tazjin): Can this one be removed somehow?
[ "ops" "nixos" ]
[ "ops" "machines" "all-systems" ]
];
}; };
readTree' = import ./nix/readTree {}; # Disallow access to //corp from other depot parts.
corpFilter = readTree.restrictFolder {
folder = "corp";
reason = ''
Code under //corp may use incompatible licensing terms with
other depot parts and should not be used anywhere else.
'';
localPkgs = readTree: { exceptions = [
fun = readTree ./fun; # For the same reason as above, whitby is exempt to serve the
lisp = readTree ./lisp; # corp website.
net = readTree ./net; [ "ops" "machines" "whitby" ]
nix = readTree ./nix; [ "ops" "nixos" ]
ops = readTree ./ops; [ "ops" "machines" "all-systems" ]
third_party = readTree ./third_party; ];
tools = readTree ./tools; };
users = readTree ./users;
web = readTree ./web; readDepot = depotArgs: readTree {
args = depotArgs;
path = ./.;
filter = parts: args: corpFilter parts (usersFilter parts args);
scopedArgs = {
__findFile = _: _: throw "Do not import from NIX_PATH in the depot!";
};
}; };
# To determine build targets, we walk through the depot tree and # To determine build targets, we walk through the depot tree and
# fetch attributes that were imported by readTree and are buildable. # fetch attributes that were imported by readTree and are buildable.
# #
# Any build target that contains `meta.ci = false` will be skipped. # Any build target that contains `meta.ci.skip = true` will be skipped.
# Is this tree node eligible for build inclusion? # Is this tree node eligible for build inclusion?
eligible = node: (node ? outPath) && (node.meta.ci or true); eligible = node: (node ? outPath) && !(node.meta.ci.skip or false);
# Walk the tree starting with 'node', recursively extending the list in
# of build targets with anything that looks buildable. readTree.fix (self: (readDepot {
depot = self;
# Pass third_party as 'pkgs' (for compatibility with external
# imports for certain subdirectories)
pkgs = self.third_party.nixpkgs;
# Expose lib attribute to packages.
lib = self.third_party.nixpkgs.lib;
# Pass arguments passed to the entire depot through, for packages
# that would like to add functionality based on this.
# #
# Any tree node can specify logical targets by exporting a # Note that it is intended for exceptional circumstance, such as
# 'meta.targets' attribute containing a list of keys in itself. This # debugging by bisecting nixpkgs.
# enables target specifications that do not exist on disk directly. externalArgs = args;
gather = node: }) // {
if node ? __readTree then
# Include the node itself if it is eligible.
(if eligible node then [ node ] else [])
# Include eligible children of the node
++ concatMap gather (attrValues node)
# Include specified sub-targets of the node
++ filter eligible (map
(k: (node."${k}" or {}) // {
# Keep the same tree location, but explicitly mark this
# node as a subtarget.
__readTree = node.__readTree;
__subtarget = k;
})
(node.meta.targets or []))
else [];
in fix(self: {
config = config self;
# Elevate 'lib' from nixpkgs
lib = import (self.third_party.nixpkgsSrc + "/lib");
# Expose readTree for downstream repo consumers.
readTree = {
__functor = x: (readTree' x.config);
config = self.config;
};
# Make the path to the depot available for things that might need it # Make the path to the depot available for things that might need it
# (e.g. NixOS module inclusions) # (e.g. NixOS module inclusions)
depotPath = ./.; path = self.third_party.nixpkgs.lib.cleanSourceWith {
name = "depot";
src = ./.;
filter = self.third_party.nixpkgs.lib.cleanSourceFilter;
};
# List of all buildable targets, for CI purposes. # List of all buildable targets, for CI purposes.
# #
# Note: To prevent infinite recursion, this *must* be a nested # Note: To prevent infinite recursion, this *must* be a nested
# attribute set (which does not have a __readTree attribute). # attribute set (which does not have a __readTree attribute).
ci.targets = gather (self // { ci.targets = readTree.gather eligible (self // {
# remove the pipelines themselves from the set over which to # remove the pipelines themselves from the set over which to
# generate pipelines because that also leads to infinite # generate pipelines because that also leads to infinite
# recursion. # recursion.
ops = self.ops // { pipelines = null; }; ops = self.ops // { pipelines = null; };
# remove nixpkgs from the set, for obvious reasons.
third_party = self.third_party // { nixpkgs = null; };
}); });
}
# Add local packages as structured by readTree # Derivation that gcroots all depot targets.
// (localPkgs (readTree' self.config)) ci.gcroot = with self.third_party.nixpkgs; makeSetupHook
{
# Load overrides into the top-level. name = "depot-gcroot";
# deps = self.ci.targets;
# This can be used to move things from third_party into the top-level, too (such }
# as `lib`). emptyFile;
// (readTree' { depot = self; pkgs = self.third_party; }) ./overrides })
)

View file

@ -55,12 +55,13 @@ Where `type` can be one of:
* `refactor`: Hopefully self-explanatory! * `refactor`: Hopefully self-explanatory!
* `test`: Added missing tests / fixed tests * `test`: Added missing tests / fixed tests
* `chore`: Maintenance work * `chore`: Maintenance work
* `subtree`: Operations involving `git subtree`
And `scope` should refer to some kind of logical grouping inside of the project. And `scope` should refer to some kind of logical grouping inside of the project.
It does not make sense to include the full path unless it aids in It does not make sense to include the full path unless it aids in
disambiguating. For example, when changing the configuration of the host disambiguating. For example, when changing the configuration of the host
`camden` at `//ops/nixos/camden` it is enough to write `feat(camden): ...`. `whitby` at `//ops/machines/whitby` it is enough to write `feat(whitby): ...`.
Please take a look at the existing commit log for examples. Please take a look at the existing commit log for examples.

View file

@ -111,9 +111,9 @@ themselves**.
If you would like to have an account on the Gerrit instance, follow these If you would like to have an account on the Gerrit instance, follow these
instructions: instructions:
1. Be a member of `##tvl`. 1. Be a member of `#tvl` on [hackint][].
2. Clone the depot locally (via `git clone "https://cl.tvl.fyi/depot"`). 2. Clone the depot locally (via `git clone "https://cl.tvl.fyi/depot"`).
3. Create a user entry in our LDAP server in [tvl-slapd/default.nix][tvl-slapd]. 3. Create a user entry in our LDAP server in [ops/users][ops-users].
We recommend using ARGON2 password hashes, which can be created We recommend using ARGON2 password hashes, which can be created
with the `slappasswd` tool if OpenLDAP was compiled with ARGON2 with the `slappasswd` tool if OpenLDAP was compiled with ARGON2
@ -128,7 +128,7 @@ instructions:
You can probably create ARGON2 hashes with other tools, but that is You can probably create ARGON2 hashes with other tools, but that is
your job to figure out. your job to figure out.
4. Create a commit adding yourself (see e.g. 4. Create a commit adding yourself (see e.g.
[CL/223](https://cl.tvl.fyi/c/depot/+/223)). [CL/2671](https://cl.tvl.fyi/c/depot/+/2671))
5. Submit the commit via email (see below). 5. Submit the commit via email (see below).
## Submitting changes via email ## Submitting changes via email
@ -149,5 +149,6 @@ The email address is a [public group][].
[Gerrit walkthrough]: https://gerrit-review.googlesource.com/Documentation/intro-gerrit-walkthrough.html [Gerrit walkthrough]: https://gerrit-review.googlesource.com/Documentation/intro-gerrit-walkthrough.html
[OWNERS]: https://cl.tvl.fyi/plugins/owners/Documentation/config.md [OWNERS]: https://cl.tvl.fyi/plugins/owners/Documentation/config.md
[guidelines]: ./CONTRIBUTING.md#commit-messages [guidelines]: ./CONTRIBUTING.md#commit-messages
[tvl-slapd]: ../ops/nixos/tvl-slapd/default.nix [ops-users]: ../ops/users/default.nix
[public group]: https://groups.google.com/a/tazj.in/forum/?hl=en#!forum/depot [public group]: https://groups.google.com/a/tazj.in/forum/?hl=en#!forum/depot
[hackint]: https://hackint.org

View file

@ -9,7 +9,7 @@ Open items:
the closure of a given source directory, using [depot-scan]. the closure of a given source directory, using [depot-scan].
```bash ```bash
DEPOT_ROOT="${depot.depotPath}" DEPOT_ROOT="${depot.path}"
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
CLIENT_ROOT="$XDG_DATA_HOME/tvlc/clients" CLIENT_ROOT="$XDG_DATA_HOME/tvlc/clients"
NICE_CHECKOUT_ROOT="$HOME/tvlc" NICE_CHECKOUT_ROOT="$HOME/tvlc"

View file

@ -1,13 +0,0 @@
{ depot, ... }:
depot.nix.buildGo.program {
name = "amsterdump";
srcs = [
./main.go
];
deps = with depot.third_party; [
# gopkgs."golang.org".x.oauth2.google
gopkgs."googlemaps.github.io".maps
];
}

File diff suppressed because it is too large Load diff

View file

@ -1,108 +0,0 @@
// Amsterdump is a small program that populates a BigQuery table with
// a matrix of origin points scattered around Amsterdam and each
// respective points travel time to a given destination.
//
// The two destinations used here are the Schiphol Airport and
// Amsterdam Central station.
//
// To accomplish this the Google Maps Distance Matrix API [1] is
// queried with the points. A visualisation is later done using
// BigQuery GeoViz[2].
//
// [1]: https://developers.google.com/maps/documentation/distance-matrix/start#quotas
// [2]: https://bigquerygeoviz.appspot.com/
package main
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"googlemaps.github.io/maps"
)
func failOn(err error, msg string) {
if err != nil {
log.Fatalln(msg, err)
}
}
type LocationResult struct {
Address string `json:"address"`
URL string `json:"url"`
Schiphol *maps.DistanceMatrixElement `json:"schiphol"`
Centraal *maps.DistanceMatrixElement `json:"centraal"`
}
type Listing struct {
URL string `json:"url"`
Address string `json:"address"`
}
func requestMatrix(ctx context.Context, client *maps.Client, listings []Listing) {
origins := make([]string, len(listings))
for i, l := range listings {
origins[i] = l.Address
}
request := &maps.DistanceMatrixRequest{
Mode: maps.TravelModeTransit,
Units: maps.UnitsMetric,
Origins: origins,
Destinations: []string{
"Schiphol Airport",
"Amsterdam Centraal",
},
}
response, err := client.DistanceMatrix(ctx, request)
failOn(err, "could not retrieve distance matrix:")
for idx, addr := range response.OriginAddresses {
result := LocationResult{
Address: addr,
URL: listings[idx].URL,
Schiphol: response.Rows[idx].Elements[0],
Centraal: response.Rows[idx].Elements[1],
}
j, _ := json.Marshal(result)
fmt.Println(string(j))
}
}
func main() {
var listings []Listing
input, err := ioutil.ReadFile("fun/amsterdump/input.json")
failOn(err, "could not read input file:")
err = json.Unmarshal(input, &listings)
failOn(err, "could not deserialise listings:")
ctx := context.Background()
apiKey := os.Getenv("MAPS_API_KEY")
if apiKey == "" {
log.Fatalln("API key must be supplied via MAPS_API_KEY")
}
client, err := maps.NewClient(maps.WithAPIKey(apiKey))
failOn(err, "could not create Google Maps API client:")
var chunk []Listing
for _, l := range listings {
if len(chunk) == 25 {
requestMatrix(ctx, client, chunk)
chunk = []Listing{}
} else {
chunk = append(chunk, l)
}
}
if len(chunk) > 1 {
requestMatrix(ctx, client, chunk)
}
}

View file

@ -1,25 +0,0 @@
;; Scraping funda.nl (this file is just notes and snippets, not full code)
;;
;; Begin by copying whole page into buffer (out of inspect element
;; because encoding is difficult)
(beginning-of-buffer)
;; zap everything that isn't a relevant result
(keep-lines "data-object-url-tracking\\|img alt")
;; mark all spans, move them to the end of the buffer
(cl-letf (((symbol-function 'read-regexp)
(lambda (&rest _) "</span>")))
(mc/mark-all-in-region-regexp (point-min) (point-max)))
;; mark all images lines (these contain street addresses for things
;; with images), clear up and join with previous
;;
;; mark all: data-image-error-fallback
;; delete all lines that don't either contain a span or an img tag
;; (there are duplicates)
(keep-lines "span class\\|img alt")
;; do some manual cleanup from the hrefs and done

View file

@ -1,22 +0,0 @@
# Solutions for Advent of Code 2019, written in Emacs Lisp.
#
# For each day a new file is created as "solution-day$n.el".
{ depot, ... }:
let
inherit (builtins) attrNames filter head listToAttrs match readDir;
dir = readDir ./.;
matchSolution = match "solution-(.*)\.el";
isSolution = f: (matchSolution f) != null;
getDay = f: head (matchSolution f);
solutionFiles = filter (e: dir."${e}" == "regular" && isSolution e) (attrNames dir);
solutions = map (f: let day = getDay f; in {
name = day;
value = depot.writeElispBin { # TODO(tazjin): move writeElispBin to depot.nix
name = "aoc2019";
deps = p: with p; [ dash s ht ];
src = ./. + ("/" + f);
};
}) solutionFiles;
in listToAttrs solutions

View file

@ -6,6 +6,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net"
"os" "os"
"os/signal" "os/signal"
"strings" "strings"
@ -27,17 +28,20 @@ var (
gerritAuthUsername = flag.String("gerrit_ssh_auth_username", "", "Gerrit SSH username") gerritAuthUsername = flag.String("gerrit_ssh_auth_username", "", "Gerrit SSH username")
gerritAuthKeyPath = flag.String("gerrit_ssh_auth_key", "", "Gerrit SSH private key path") gerritAuthKeyPath = flag.String("gerrit_ssh_auth_key", "", "Gerrit SSH private key path")
ircServer = flag.String("irc_server", "chat.freenode.net:7000", "IRC server to connect to") ircServer = flag.String("irc_server", "irc.hackint.org:6697", "IRC server to connect to")
ircTls = flag.Bool("irc_tls", false, "Does the server connection need TLS?")
ircNick = flag.String("irc_nick", "clbot", "Nick to use when connecting to IRC") ircNick = flag.String("irc_nick", "clbot", "Nick to use when connecting to IRC")
ircUser = flag.String("irc_user", "clbot", "User string to use for IRC") ircUser = flag.String("irc_user", "clbot", "User string to use for IRC")
ircName = flag.String("irc_name", "clbot", "Name string to use for IRC") ircName = flag.String("irc_name", "clbot", "Name string to use for IRC")
ircChannel = flag.String("irc_channel", "##tvl", "Channel to send messages to") ircChannel = flag.String("irc_channel", "#tvl", "Channel to send messages to")
ircPassword = flag.String("irc_pass", "", "Password to use for IRC") ircPassword = flag.String("irc_pass", "", "Password to use for IRC")
ircSendLimit = flag.Duration("irc_send_limit", 100*time.Millisecond, "Delay between messages") ircSendLimit = flag.Duration("irc_send_limit", 100*time.Millisecond, "Delay between messages")
ircSendBurst = flag.Int("irc_send_burst", 10, "Number of messages which can be sent in a burst") ircSendBurst = flag.Int("irc_send_burst", 10, "Number of messages which can be sent in a burst")
notifyRepo = flag.String("notify_repo", "depot", "Repo name to notify about") notifyRepo = flag.String("notify_repo", "depot", "Repo name to notify about")
notifyBranches = stringSetFlag{} notifyBranches = stringSetFlag{}
neverPing = flag.String("never_ping", "marcus", "Comma-separated terms that should never ping users")
) )
func init() { func init() {
@ -111,11 +115,21 @@ func runIRC(ctx context.Context, ircCfg irc.ClientConfig, sendMsg <-chan string)
(func() { (func() {
connectedStart := time.Now() connectedStart := time.Now()
ircConn, err := tls.Dial("tcp", *ircServer, nil)
var ircConn net.Conn
var err error
if *ircTls {
ircConn, err = tls.Dial("tcp", *ircServer, nil)
} else {
ircConn, err = net.Dial("tcp", *ircServer)
}
if err != nil { if err != nil {
log.Errorf("connecting to IRC at tcp/%s: %v", *ircServer, err) log.Errorf("connecting to IRC at tcp/%s (tls: %v): %v", *ircServer, *ircTls, err)
return return
} }
ircClient := irc.NewClient(ircConn, ircCfg) ircClient := irc.NewClient(ircConn, ircCfg)
ircClientCtx, cancel := context.WithCancel(ctx) ircClientCtx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
@ -144,11 +158,11 @@ func runIRC(ctx context.Context, ircCfg irc.ClientConfig, sendMsg <-chan string)
} }
} }
func username(p gerritevents.PatchSet) string { func username(a gerritevents.Account) string {
options := []string{ options := []string{
p.Uploader.Username, a.Username,
p.Uploader.Name, a.Name,
p.Uploader.Email, a.Email,
} }
for _, opt := range options { for _, opt := range options {
if opt != "" { if opt != "" {
@ -165,6 +179,20 @@ func noping(user string) string {
return string(un[0:1]) + zeroWidthSpace + string(un[1:]) return string(un[0:1]) + zeroWidthSpace + string(un[1:])
} }
// Apply noping to each instance of the username in the supplied
// message. With this users will not be pinged for their own CLs, but
// they will be notified if someone else writes a CL that includes
// their username.
//
// Also applies noping to all instances of the words in `neverPing`.
func nopingAll(username, message string) string {
for _, word := range strings.Split(*neverPing, ",") {
message = strings.ReplaceAll(message, word, noping(word))
}
return strings.ReplaceAll(message, username, noping(username))
}
func patchSetURL(c gerritevents.Change, p gerritevents.PatchSet) string { func patchSetURL(c gerritevents.Change, p gerritevents.PatchSet) string {
return fmt.Sprintf("https://cl.tvl.fyi/%d", c.Number) return fmt.Sprintf("https://cl.tvl.fyi/%d", c.Number)
} }
@ -223,12 +251,21 @@ func main() {
if e.Change.Project != *notifyRepo || !notifyBranches[e.Change.Branch] || e.PatchSet.Number != 1 { if e.Change.Project != *notifyRepo || !notifyBranches[e.Change.Branch] || e.PatchSet.Number != 1 {
continue continue
} }
parsedMsg = fmt.Sprintf("CL/%d: %q proposed by %s - %s", e.Change.Number, e.Change.Subject, noping(username(e.PatchSet)), patchSetURL(e.Change, e.PatchSet)) user := username(e.PatchSet.Uploader)
parsedMsg = nopingAll(user, fmt.Sprintf("CL/%d proposed by %s - %s - %s", e.Change.Number, user, e.Change.Subject, patchSetURL(e.Change, e.PatchSet)))
case *gerritevents.ChangeMerged: case *gerritevents.ChangeMerged:
if e.Change.Project != *notifyRepo || !notifyBranches[e.Change.Branch] { if e.Change.Project != *notifyRepo || !notifyBranches[e.Change.Branch] {
continue continue
} }
parsedMsg = fmt.Sprintf("CL/%d: %q applied by %s - %s", e.Change.Number, e.Change.Subject, noping(username(e.PatchSet)), patchSetURL(e.Change, e.PatchSet)) owner := username(e.Change.Owner)
submitter := e.Submitter.Username
url := patchSetURL(e.Change, e.PatchSet)
if submitter != owner && submitter == "clbot" {
parsedMsg = nopingAll(owner, fmt.Sprintf("CL/%d by %s autosubmitted - %s - %s", e.Change.Number, owner, e.Change.Subject, url))
} else {
parsedMsg = nopingAll(owner, fmt.Sprintf("CL/%d applied by %s - %s - %s", e.Change.Number, owner, e.Change.Subject, url))
}
} }
if parsedMsg != "" { if parsedMsg != "" {
sendMsgChan <- parsedMsg sendMsgChan <- parsedMsg

View file

@ -4,17 +4,17 @@ use std::rc::Rc;
use std::sync::RwLock; use std::sync::RwLock;
struct Defer<F: Fn()> { struct Defer<F: Fn()> {
f: F f: F,
} }
impl <F: Fn()> Drop for Defer<F> { impl<F: Fn()> Drop for Defer<F> {
fn drop(&mut self) { fn drop(&mut self) {
(self.f)() (self.f)()
} }
} }
// Only added this for Go-syntax familiarity ;-) // Only added this for Go-syntax familiarity ;-)
fn defer<F: Fn()>(f: F) -> Defer<F> { fn defer<F: Fn()>(f: F) -> Defer<F> {
Defer { f } Defer { f }
} }
@ -29,7 +29,9 @@ type ErrorHandle<T> = Rc<RwLock<Option<T>>>;
/////////////////// ///////////////////
#[derive(Debug)] // Debug trait for some default way to print the type. #[derive(Debug)] // Debug trait for some default way to print the type.
enum Error { DropError } enum Error {
DropError,
}
fn main() { fn main() {
// Create a place to store the error. // Create a place to store the error.
@ -60,7 +62,7 @@ fn main() {
match *drop_err.read().unwrap() { match *drop_err.read().unwrap() {
Some(ref err) => println!("Oh no, an error occured: {:?}!", err), Some(ref err) => println!("Oh no, an error occured: {:?}!", err),
None => println!("Phew, everything went well.") None => println!("Phew, everything went well."),
}; };
} }

View file

@ -1,17 +1,17 @@
// Go's defer in Rust! // Go's defer in Rust!
struct Defer<F: Fn()> { struct Defer<F: Fn()> {
f: F f: F,
} }
impl <F: Fn()> Drop for Defer<F> { impl<F: Fn()> Drop for Defer<F> {
fn drop(&mut self) { fn drop(&mut self) {
(self.f)() (self.f)()
} }
} }
// Only added this for Go-syntax familiarity ;-) // Only added this for Go-syntax familiarity ;-)
fn defer<F: Fn()>(f: F) -> Defer<F> { fn defer<F: Fn()>(f: F) -> Defer<F> {
Defer { f } Defer { f }
} }

View file

@ -1,10 +1,10 @@
// Go's defer in Rust, with a little twist! // Go's defer in Rust, with a little twist!
struct Defer<F: Fn()> { struct Defer<F: Fn()> {
f: F f: F,
} }
impl <F: Fn()> Drop for Defer<F> { impl<F: Fn()> Drop for Defer<F> {
fn drop(&mut self) { fn drop(&mut self) {
(self.f)() (self.f)()
} }

View file

@ -1,8 +1,8 @@
{ depot, ... }: { depot, pkgs, ... }:
let let
inherit (depot) elmPackages; inherit (pkgs) cacert iana-etc libredirect stdenv runCommandNoCC writeText;
inherit (depot.third_party) cacert iana-etc libredirect stdenv runCommandNoCC writeText; elmPackages = depot.third_party.elmPackages_0_18;
frontend = stdenv.mkDerivation { frontend = stdenv.mkDerivation {
name = "gemma-frontend.html"; name = "gemma-frontend.html";
@ -33,7 +33,8 @@ let
cp ${frontend} $out/index.html cp ${frontend} $out/index.html
''}/") ''}/")
''; '';
in depot.nix.buildLisp.program { in
depot.nix.buildLisp.program {
name = "gemma"; name = "gemma";
deps = with depot.third_party.lisp; [ deps = with depot.third_party.lisp; [
@ -47,4 +48,10 @@ in depot.nix.buildLisp.program {
./src/gemma.lisp ./src/gemma.lisp
injectFrontend injectFrontend
]; ];
# depends on SBCL
brokenOn = [
"ccl"
"ecl"
];
} }

View file

@ -1,22 +1,23 @@
{ pkgs, lib, ... }: { depot, pkgs, lib, ... }:
let let
inherit (pkgs) python python3 python3Packages; inherit (pkgs) python3 python3Packages;
opts = { opts = {
pname = "idualctl"; pname = "idualctl";
version = "0.1"; version = "0.1";
src = ./.; src = ./.;
propagatedBuildInputs = [ propagatedBuildInputs = [
python.broadlink depot.third_party.python.broadlink
]; ];
}; };
package = python3Packages.buildPythonPackage opts; package = python3Packages.buildPythonPackage opts;
script = python3Packages.buildPythonApplication opts; script = python3Packages.buildPythonApplication opts;
in { in
depot.nix.readTree.drvTargets {
inherit script; inherit script;
python = python3.withPackages (_: [ package ]); python = python3.withPackages (_: [ package ]);
setAlarm = pkgs.writeShellScriptBin "set-alarm" '' setAlarm = pkgs.writeShellScriptBin "set-alarm" ''
echo "setting an alarm for ''${1}" echo "setting an alarm for ''${1}"
${pkgs.systemd}/bin/systemd-run --user --on-calendar="''${1} Europe/London" --unit=light-alarm.service ${pkgs.systemd}/bin/systemd-run --user --on-calendar="''${1} Europe/London" --unit=light-alarm.service

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

13
third_party/tvl/fun/owothia/default.nix vendored Normal file
View file

@ -0,0 +1,13 @@
{ depot ? (import ../../../. { })
, pkgs ? depot.third_party.nixpkgs
, ...
}:
let
basePkg = pkgs.haskellPackages.callPackage ./pkg.nix { };
in
pkgs.haskell.lib.overrideSrc basePkg {
src = depot.third_party.gitignoreSource ./.;
version = "canon";
}

34
third_party/tvl/fun/owothia/pkg.nix vendored Normal file
View file

@ -0,0 +1,34 @@
{ mkDerivation
, base
, bytestring
, chatter
, containers
, envy
, irc-client
, lens
, lib
, random
, relude
, text
}:
mkDerivation {
pname = "owothia";
version = "0.0.1.0";
src = ./.;
isLibrary = false;
isExecutable = true;
executableHaskellDepends = [
base
bytestring
chatter
containers
envy
irc-client
lens
random
relude
text
];
license = "unknown";
hydraPlatforms = lib.platforms.none;
}

22
third_party/tvl/fun/owothia/shell.nix vendored Normal file
View file

@ -0,0 +1,22 @@
{ pkgs ? (import ../../../. { }).third_party, ... }:
let
inherit (pkgs)
haskellPackages
haskell
gitignoreSource
;
in
(haskellPackages.extend (haskell.lib.packageSourceOverrides {
owothia = gitignoreSource ./.;
})).shellFor {
packages = p: [ p.owothia ];
withHoogle = true;
doBenchmark = true;
buildInputs = with haskellPackages; [
cabal-install
hlint
haskell-language-server
];
}

168
third_party/tvl/fun/owothia/src/Main.hs vendored Normal file
View file

@ -0,0 +1,168 @@
{-# LANGUAGE TemplateHaskell #-}
module Main where
import Network.IRC.Client
import Control.Lens
import NLP.POS
import NLP.Types (POSTagger)
import qualified NLP.Types.Tags as Tags
import NLP.Types.Tree
import qualified NLP.Corpora.Conll as Conll
import NLP.Corpora.Conll (Tag)
import qualified Data.ByteString as BS
import System.Random
import System.Envy
import System.IO as S
import Data.Maybe
import Data.Foldable (traverse_)
import qualified Data.Text
--------------------------------------------------------------------------------
data Config = Config
{ _owoChance :: Int
, _ircServer :: ByteString
, _ircPort :: Int
, _ircServerPassword :: Maybe Text
, _nickservPassword :: Maybe Text
, _ircNick :: Maybe Text
, _ircIdent :: Maybe Text
, _ircChannels :: [Text]
}
deriving stock (Show, Eq, Generic)
makeLenses ''Config
instance Var [Text] where
toVar ts = show ts
fromVar s = readMaybe s >>= (pure . map Data.Text.pack)
instance FromEnv Config where
fromEnv _ =
Config <$> env "OWO_CHANCE"
<*> env "IRC_SERVER"
<*> env "IRC_PORT"
<*> envMaybe "IRC_SERVER_PASSWORD"
<*> envMaybe "NICKSERV_PASSWORD"
<*> envMaybe "IRC_NICK"
<*> envMaybe "IRC_IDENT"
<*> env "IRC_CHANNELS"
stopWord :: Text -> Bool
stopWord "'s" = True
stopWord "\"" = True
stopWord "is" = True
stopWord "are" = True
stopWord "am" = True
stopWord "were" = True
stopWord "was" = True
stopWord "be" = True
stopWord _ = False
pickVerb :: POS Tag -> Maybe Text
pickVerb (POS Conll.VB (Token verb)) = Just verb
pickVerb (POS Conll.VBD (Token verb)) = Just verb
pickVerb (POS Conll.VBG (Token verb)) = Just verb
pickVerb (POS Conll.VBN (Token verb)) = Just verb
pickVerb (POS Conll.VBZ (Token verb)) = Just verb
pickVerb _ = Nothing
pickNoun :: POS Tag -> Maybe Text
pickNoun (POS Conll.NN (Token noun)) = Just noun
pickNoun _ = Nothing
randomPOS
:: Tags.Tag tag
=> (POS tag -> Maybe Text)
-> POSTagger tag
-> Text
-> IO (Maybe Text)
randomPOS pickPOS tagger s = do
let candidates
= filter (not . stopWord)
. mapMaybe pickPOS
$ tag tagger s >>= \(TaggedSent ps) -> ps
i <- randomRIO (0, length candidates - 1)
pure $ candidates ^? ix i
doOwo :: MonadIO m => Config -> m Bool
doOwo conf = do
n <- liftIO (randomRIO @Int (0, conf ^. owoChance))
pure $ n == 0
data OwoType = Noun | Verb
deriving stock (Show, Eq)
instance Random OwoType where
random = over _1 (bool Noun Verb) . random
randomR = const random
vowels :: [Char]
vowels = "aeiou"
article :: Text -> Text
article (x :< _) | x `elem` vowels = "an"
article _ = "a"
owo :: OwoType -> Text -> Text
owo Noun n = mconcat
[ "I'm "
, article n
, " "
, n
, if "o" `Data.Text.isSuffixOf` n
then "wo"
else " owo"
]
owo Verb v = v <> " me owo"
pickOwo :: OwoType -> POS Tag -> Maybe Text
pickOwo Verb = pickVerb
pickOwo Noun = pickNoun
randomOwo :: OwoType -> POSTagger Tag -> Text -> IO (Maybe Text)
randomOwo = randomPOS . pickOwo
owothiaHandler :: Config -> Text -> IORef Bool -> POSTagger Tag -> EventHandler s
owothiaHandler conf nick state tagger = EventHandler Just $ \src ev -> do
hasIdentified <- readIORef state
when (not hasIdentified) $ do
nickservAuth
traverse_ (send . Join) (conf ^. ircChannels)
writeIORef state True
when ("You are now identified" `BS.isInfixOf` (ev ^. raw)) $
traverse_ (send . Join) (conf ^. ircChannels)
case (src, ev ^. message) of
(Channel chan nick, Privmsg _ (Right m)) -> do
willOwo <- doOwo conf
when willOwo $ owoMessage chan m
_ -> pure()
pure ()
where
owoMessage chan m = do
owoType <- liftIO randomIO
mWord <- liftIO $ randomOwo owoType tagger m
for_ mWord $ \word -> send $ Privmsg chan $ Right $ owo owoType word
nickservAuthMsg = "IDENTIFY " <> nick <> " " <> fromJust (conf ^. nickservPassword)
nickservAuth = send $ Privmsg "NickServ" $ Right nickservAuthMsg
main :: IO ()
main = do
conf <- either fail pure =<< decodeEnv
tagger <- defaultTagger
state <- newIORef $ not . isJust $ (conf ^. nickservPassword)
S.hSetBuffering stdout LineBuffering
let nick = fromMaybe "owothia" (conf ^. ircNick)
conn =
plainConnection (conf ^. ircServer) (conf ^. ircPort)
& realname .~ "Owothia Revströwö"
& password .~ (conf ^. ircServerPassword)
& username .~ fromMaybe "owothia" (conf ^. ircIdent)
& logfunc .~ stdoutLogger
cfg =
defaultInstanceConfig nick
& channels .~ (conf ^. ircChannels)
& handlers %~ (owothiaHandler conf nick state tagger : )
runClient conn cfg ()

1336
third_party/tvl/fun/paroxysm/Cargo.lock generated vendored

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,6 @@
{ depot, ... }: { depot, pkgs, ... }:
let depot.third_party.naersk.buildPackage {
pkgs = depot.third_party;
in
pkgs.naersk.buildPackage {
name = "paroxysm"; name = "paroxysm";
version = "0.0.2"; version = "0.0.2";
src = ./.; src = ./.;

View file

@ -1,6 +1,6 @@
{ depot, ... }: { depot, pkgs, ... }:
depot.third_party.dockerTools.buildLayeredImage { pkgs.dockerTools.buildLayeredImage {
name = "paroxysm"; name = "paroxysm";
contents = [ depot.fun.paroxysm ]; contents = [ depot.fun.paroxysm ];
config.Entrypoint = [ "${depot.fun.paroxysm}/bin/paroxysm" ]; config.Entrypoint = [ "${depot.fun.paroxysm}/bin/paroxysm" ];

View file

@ -1,8 +1,7 @@
use crate::models::{Entry, Keyword, NewEntry, NewKeyword}; use crate::models::{Entry, Keyword, NewEntry, NewKeyword};
use diesel::pg::PgConnection; use diesel::pg::PgConnection;
use diesel::prelude::*; use diesel::prelude::*;
use failure::format_err; use failure::{format_err, Error};
use failure::Error;
use std::borrow::Cow; use std::borrow::Cow;
/// Maximum number of times we'll follow a `see: ` pointer. /// Maximum number of times we'll follow a `see: ` pointer.

View file

@ -7,8 +7,7 @@ use crate::cfg::Config;
use crate::keyword::KeywordDetails; use crate::keyword::KeywordDetails;
use diesel::pg::PgConnection; use diesel::pg::PgConnection;
use diesel::r2d2::{ConnectionManager, Pool}; use diesel::r2d2::{ConnectionManager, Pool};
use failure::format_err; use failure::{format_err, Error};
use failure::Error;
use irc::client::prelude::*; use irc::client::prelude::*;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::{debug, info, warn}; use log::{debug, info, warn};
@ -153,8 +152,9 @@ impl App {
// Use `nick` here, so things like "grfn: see glittershark" work. // Use `nick` here, so things like "grfn: see glittershark" work.
let val = if let Some(last) = chan_lastmsgs.get(nick_to_grab) { let val = if let Some(last) = chan_lastmsgs.get(nick_to_grab) {
if last.starts_with("\x01ACTION ") { if last.starts_with("\x01ACTION ") {
// Yes, this is inefficient, but it's better than writing some hacky CTCP parsing code // Yes, this is inefficient, but it's better than writing some hacky CTCP parsing
// I guess (also, characters are hard, so just blindly slicing seems like a bad idea) // code I guess (also, characters are hard, so just blindly slicing
// seems like a bad idea)
format!( format!(
"* {} {}", "* {} {}",
nick_to_grab, nick_to_grab,
@ -208,7 +208,7 @@ impl App {
pub fn handle_query( pub fn handle_query(
&mut self, &mut self,
target: &str, target: &str,
nick: &str, _nick: &str,
chan: &str, chan: &str,
query: Captures, query: Captures,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -239,7 +239,7 @@ impl App {
} }
// step 2: attempt to POST it to eta's pastebin // step 2: attempt to POST it to eta's pastebin
// TODO(eta): make configurable // TODO(eta): make configurable
let response = crimp::Request::put("https://theta.eu.org/lx/upload") let response = crimp::Request::put("https://eta.st/lx/upload")
.user_agent("paroxysm/0.0.2 crimp/0.2")? .user_agent("paroxysm/0.0.2 crimp/0.2")?
.header("Linx-Expiry", "7200")? // 2 hours .header("Linx-Expiry", "7200")? // 2 hours
.body("text/plain", data_to_upload.as_bytes()) .body("text/plain", data_to_upload.as_bytes())
@ -301,8 +301,12 @@ impl App {
} }
} }
None => { None => {
self.client // If someone just posts "??????????", don't spam the channel with
.send_notice(target, format!("\x02{}\x0f: never heard of it", subj))?; // an error message (but do allow joke entries to appear if set).
if !subj.chars().all(|c| c == '?' || c == ' ') {
self.client
.send_notice(target, format!("\x02{}\x0f: never heard of it", subj))?;
}
} }
} }
Ok(()) Ok(())

View file

@ -1,19 +0,0 @@
{ depot, ... }:
depot.third_party.writeShellScriptBin "start-tvl-stream" ''
env LD_LIBRARY_PATH=/run/opengl-driver/lib/ ${depot.third_party.ffmpeg}/bin/ffmpeg \
-vsync 0 \
-hwaccel cuvid \
-init_hw_device cuda=0 -filter_hw_device 0 \
-f x11grab \
-video_size 1920x1080 \
-framerate 60 \
-thread_queue_size 256 \
-i :0.0+0,0 \
-filter:v "format=nv12,hwupload,scale_npp=w=1280:h=720:interp_algo=lanczos" \
-c:v h264_nvenc \
-preset:v llhq \
-rc:v cbr_ld_hq \
-an \
-f flv rtmp://tazj.in:1935/tvl
''

View file

@ -20,7 +20,8 @@ type incomingIRC struct {
User string `json:"User"` User string `json:"User"`
} }
var supressionUsernames map[string]bool var suppressionUsernames map[string]bool
var noMkov map[string]bool
func main() { func main() {
redisc := redis.NewClient(&redis.Options{ redisc := redis.NewClient(&redis.Options{
@ -30,7 +31,27 @@ func main() {
}) })
fireaway := make(chan incomingIRC, 10) fireaway := make(chan incomingIRC, 10)
supressionUsernames = make(map[string]bool) suppressionUsernames = make(map[string]bool)
suppressionList := redisc.HGetAll("suppressionList")
suppressionListA, _ := suppressionList.Result()
suppressionListMap, _ := stringMaptoIntMap(suppressionListA)
for v, _ := range suppressionListMap {
suppressionUsernames[v] = true
suppressionUsernames[strings.ToLower(v)] = true
}
noMkov = make(map[string]bool)
noMkovRedis := redisc.HGetAll("nomkov")
noMkovRedisA, _ := noMkovRedis.Result()
noMkovMap, _ := stringMaptoIntMap(noMkovRedisA)
for v, _ := range noMkovMap {
noMkov[v] = true
noMkov[strings.ToLower(v)] = true
}
go func() { go func() {
for { for {
@ -81,8 +102,11 @@ func main() {
func generateMesasge(msg incomingIRC, redisc *redis.Client) string { func generateMesasge(msg incomingIRC, redisc *redis.Client) string {
text := msg.Params[1] text := msg.Params[1]
username := strings.ToLower(msg.Name) username := strings.ToLower(msg.Name)
supressionUsernames[username] = true suppressionUsernames[username] = true
supressionUsernames[username+":"] = true suppressionUsernames[username+":"] = true
suppressionUsernames[msg.Name] = true
suppressionUsernames[msg.Name+":"] = true
redisc.HIncrBy("suppressionList", msg.Name, 1)
text = strings.ToLower(text) text = strings.ToLower(text)
text = strings.Replace(text, ",", "", -1) text = strings.Replace(text, ",", "", -1)
@ -92,16 +116,19 @@ func generateMesasge(msg incomingIRC, redisc *redis.Client) string {
text = strings.Replace(text, "?", "", -1) text = strings.Replace(text, "?", "", -1)
words := strings.Split(text, " ") words := strings.Split(text, " ")
lastWord := propwords(username, words[0], redisc) lastWord := propwords(msg.Name, words[0], redisc)
if supressionUsernames[words[0]] { if noMkov[username] {
if len(words[0]) < 2 { lastWord = blockoutWord(lastWord)
words[0] = "vee" words[0] = blockoutWord(words[0])
}
words[0] = fmt.Sprintf("%s.%s", string(words[0][0]), words[0][1:])
} }
lastWord = filterHighlights(lastWord)
if lastWord == "_END_" { if lastWord == "_END_" {
if noMkov[username] {
return blockoutWord(words[0])
}
return words[0] return words[0]
} }
outputMsg := words[0] + " " + lastWord + " " outputMsg := words[0] + " " + lastWord + " "
@ -112,13 +139,12 @@ func generateMesasge(msg incomingIRC, redisc *redis.Client) string {
return outputMsg return outputMsg
} }
if supressionUsernames[lastWord] { if noMkov[username] {
if len(lastWord) < 2 { lastWord = blockoutWord(lastWord)
lastWord = "vee"
}
lastWord = fmt.Sprintf("%s.%s", string(lastWord[0]), lastWord[1:])
} }
lastWord = filterHighlights(lastWord)
outputMsg += lastWord + " " outputMsg += lastWord + " "
if len(outputMsg) > 100 { if len(outputMsg) > 100 {
return outputMsg return outputMsg
@ -126,6 +152,28 @@ func generateMesasge(msg incomingIRC, redisc *redis.Client) string {
} }
} }
// filterHighlights: tries to prevent highlights by checking against
// a map called suppressionUsernames
func filterHighlights(in string) string {
for username := range suppressionUsernames {
if strings.Contains(in, username) {
if len(in) < 2 {
in = fmt.Sprintf("%s.%s", string(in[0]), in[1:])
return in
}
}
}
return in
}
func blockoutWord(in string) string {
block := ""
for i := 0; i < len(in); i++ {
block += "█"
}
return block
}
func propwords(username string, start string, redisc *redis.Client) string { func propwords(username string, start string, redisc *redis.Client) string {
userHash := redisc.HGetAll(fmt.Sprintf("%s-%s", username, start)) userHash := redisc.HGetAll(fmt.Sprintf("%s-%s", username, start))
userHashMap, err := userHash.Result() userHashMap, err := userHash.Result()
@ -190,7 +238,9 @@ func learnFromMessage(msg incomingIRC, redisc *redis.Client) {
nextWord = words[k+1] nextWord = words[k+1]
} }
redisc.HIncrBy(fmt.Sprintf("%s-%s", username, word), nextWord, 1) if !noMkov[username] {
redisc.HIncrBy(fmt.Sprintf("%s-%s", username, word), nextWord, 1)
}
redisc.HIncrBy(fmt.Sprintf("generic-%s", word), nextWord, 1) redisc.HIncrBy(fmt.Sprintf("generic-%s", word), nextWord, 1)
} }
} }

View file

@ -1,7 +1,8 @@
{ depot, pkgs, ... }@args: { depot, pkgs, ... }:
let let
inherit (pkgs) gopkgs; inherit (depot.third_party) gopkgs;
uggc = depot.nix.buildGo.program { uggc = depot.nix.buildGo.program {
name = "uggc"; name = "uggc";
srcs = [ srcs = [
@ -11,7 +12,8 @@ let
gopkgs."github.com".pkg.browser.gopkg gopkgs."github.com".pkg.browser.gopkg
]; ];
}; };
in uggc.overrideAttrs(old: { in
uggc.overrideAttrs (old: {
buildCommand = old.buildCommand + '' buildCommand = old.buildCommand + ''
install -D ${./uggc.desktop} $out/share/applications/uggc.desktop install -D ${./uggc.desktop} $out/share/applications/uggc.desktop
sed "s|@out@|$out|g" -i $out/share/applications/uggc.desktop sed "s|@out@|$out|g" -i $out/share/applications/uggc.desktop

3
third_party/tvl/fun/🕰️/OWNERS vendored Normal file
View file

@ -0,0 +1,3 @@
inherited: true
owners:
- sterni

91
third_party/tvl/fun/🕰️/bin.lisp vendored Normal file
View file

@ -0,0 +1,91 @@
(defpackage 🕰.bin
(:shadow :describe)
(:use :cl :opts)
(:import-from :uiop :quit)
(:import-from :local-time
:now :timestamp-subtimezone :+utc-zone+
:*default-timezone* :define-timezone)
(:import-from :klatre :format-dottime-offset)
(:import-from :🕰 :)
(:export :🚂))
(in-package :🕰.bin)
(declaim (optimize (safety 3)))
(opts:define-opts
(:name :help
:description "Print this help text"
:short #\h
:long "help")
(:name :dot-time
:description "Use pseudo dot-time format (implies -u)"
:short #\d
:long "dot-time")
(:name :utc
:description "Display time in UTC instead of local time"
:short #\u
:long "utc")
(:name :no-newline
:description "Don't print a trailing newline"
:short #\n
:long "no-newline"))
(defun make-slash-terminated (str)
(if (eq (char str (1- (length str))) #\/)
str
(concatenate 'string str "/")))
; TODO(sterni): upstream this into local-time
(defun setup-default-timezone ()
(let* ((tz (remove #\: (uiop:getenv "TZ") :count 1))
(tz-dir (uiop:getenv "TZDIR"))
(tz-file (if (and tz tz-dir)
(merge-pathnames
(pathname tz)
(pathname (make-slash-terminated tz-dir)))
(pathname "/etc/localtime"))))
(handler-case
(define-timezone *default-timezone* tz-file :load t)
(t () (setf *default-timezone* +utc-zone+)))))
(defun 🚂 ()
(let ((ts (now)))
(multiple-value-bind (options free-args)
(handler-case (opts:get-opts)
; only handle subset of conditions that can happen here
(opts:unknown-option (c)
(format t "error: unknown option ~s~%" (opts:option c))
(quit 100)))
; check if we have any free args we don't know what to do with
(when (> (length free-args) 0)
(write-string "error: unexpected command line argument(s): ")
(loop for arg in free-args
do (progn (write-string arg) (write-char #\space)))
(write-char #\newline)
(quit 100))
; print help and exit
(when (getf options :help)
(opts:describe :usage-of "🕰️")
(quit 0))
; reinit *default-timezone* as it is cached from compilation
(setup-default-timezone)
; dot-time implies UTC, naturally
(when (getf options :dot-time)
(setf (getf options :utc) t))
; print clock face
(format t "~A" ( ts (if (getf options :utc)
local-time:+utc-zone+
local-time:*default-timezone*)))
; render dot-time offset if necessary
(when (getf options :dot-time)
(multiple-value-bind (offset-secs _dst _name)
(timestamp-subtimezone ts local-time:*default-timezone*)
(write-string
(format-dottime-offset (round (/ offset-secs 3600))))))
; write newline if necessary
(when (not (getf options :no-newline))
(write-char #\newline)))))

44
third_party/tvl/fun/🕰️/default.nix vendored Normal file
View file

@ -0,0 +1,44 @@
{ depot, ... }:
let
inherit (depot.nix)
buildLisp
;
lib = buildLisp.library {
name = "lib🕰";
deps = [
depot.third_party.lisp.local-time
];
srcs = [
./lib.lisp
];
};
bin = buildLisp.program {
name = "🕰";
deps = [
depot.third_party.lisp.unix-opts
depot.lisp.klatre
{
default = buildLisp.bundled "asdf";
sbcl = buildLisp.bundled "uiop";
}
lib
];
srcs = [
./bin.lisp
];
main = "🕰.bin:🚂";
brokenOn = [
"ecl" # refuses to create non-ASCII paths even on POSIX…
];
};
in
bin // {
inherit lib;
}

32
third_party/tvl/fun/🕰️/lib.lisp vendored Normal file
View file

@ -0,0 +1,32 @@
(defpackage 🕰
(:use :cl)
(:import-from :local-time
:timestamp-subtimezone :*default-timezone* :sec-of)
(:export :))
(in-package :🕰)
(declaim (optimize (safety 3)))
(defparameter *clock-emojis*
(vector #\🕛 #\🕧 ; 00:00 - 00:30
#\🕐 #\🕜 ; 01:00 - 01:30
#\🕑 #\🕝 ; 00:00 - 00:30
#\🕒 #\🕞 ; 00:00 - 00:30
#\🕓 #\🕟 ; 00:00 - 00:30
#\🕔 #\🕠 ; 00:00 - 00:30
#\🕕 #\🕡 ; 00:00 - 00:30
#\🕖 #\🕢 ; 00:00 - 00:30
#\🕗 #\🕣 ; 00:00 - 00:30
#\🕘 #\🕤 ; 00:00 - 00:30
#\🕙 #\🕥 ; 00:00 - 00:30
#\🕚 #\🕦)) ; 11:00 - 11:30
(defun (timestamp &optional (tz *default-timezone*))
"Convert a LOCAL-TIME:TIMESTAMP into the nearest Unicode clock face.
Use TZ (which defaults to LOCAL-TIME:*DEFAULT-TIMEZONE*) to determine
the UTC offset to factor when determining the local clock face."
(let* ((offset (multiple-value-bind (offset-secs _dst _name)
(timestamp-subtimezone timestamp tz)
offset-secs))
(secs (+ (sec-of timestamp) offset)))
(elt *clock-emojis* (mod (round (/ secs 1800)) 24))))

View file

@ -14,4 +14,8 @@ depot.nix.buildLisp.library {
./message.lisp ./message.lisp
./client.lisp ./client.lisp
]; ];
brokenOn = [
"ecl" # dynamic cffi
];
} }

3
third_party/tvl/lisp/klatre/OWNERS vendored Normal file
View file

@ -0,0 +1,3 @@
inherited: true
owners:
- grfn

View file

@ -1,7 +1,8 @@
(in-package #:klatre) (in-package #:klatre)
(declaim (optimize (safety 3))) (declaim (optimize (safety 3)))
(defmacro comment (&rest _)) (defmacro comment (&rest _)
(declare (ignore _)))
(defun posp (n) (> n 0)) (defun posp (n) (> n 0))
@ -41,7 +42,7 @@
(defun chunk-list (size list &key (start 0) end) (defun chunk-list (size list &key (start 0) end)
"Returns successive chunks of list of size SIZE, starting at START and ending "Returns successive chunks of list of size SIZE, starting at START and ending
at END." at END."
(declare (inline check-list/bounded check-list/simple)) (declare (inline chunk-list/bounded chunk-list/unbounded))
(check-type size (integer 1)) (check-type size (integer 1))
(let ((list (nthcdr start list))) (let ((list (nthcdr start list)))
(when list (when list
@ -76,24 +77,36 @@ separated by SEP."
(defparameter dottime-format (defparameter dottime-format
'((:year 4) #\- (:month 2) #\- (:day 2) '((:year 4) #\- (:month 2) #\- (:day 2)
#\T #\T
(:hour 2) #\· (:min 2) "+00") ; TODO(grfn): Allow passing offset (:hour 2) #\· (:min 2))
"`:LOCAL-TIME' format specifier for dottime") "`:LOCAL-TIME' format specifier for dottime")
(defun format-dottime (timestamp) (defun format-dottime (timestamp &optional (offset 0))
"Return TIMESTAMP formatted as dottime, using a +00 offset" "Return TIMESTAMP formatted as dottime, with a specified offset or +00"
(check-type timestamp local-time:timestamp) (check-type timestamp local-time:timestamp)
(local-time:format-timestring nil timestamp (concatenate 'string
:format dottime-format (local-time:format-timestring nil timestamp
:timezone local-time:+utc-zone+)) :format dottime-format
:timezone local-time:+utc-zone+)
(format-dottime-offset offset)))
(defun format-dottime-offset (offset)
"Render OFFSET in hours in the format specified by dottime."
(check-type offset integer)
(concatenate 'string
; render sign manually since format prints it after padding
(if (>= offset 0) "+" "-")
(format nil "~2,'0D" (abs offset))))
(comment (comment
(format-dottime (local-time:now))) (format-dottime (local-time:now))
(format-dottime (local-time:now) 2))
(defun try-parse-integer (str) (defun try-parse-integer (str)
"Attempt to parse STR as an integer, returning nil if it is invalid." "Attempt to parse STR as an integer, returning nil if it is invalid."
(check-type str string) (check-type str string)
(handler-case (parse-integer str) (handler-case (parse-integer str)
(sb-int:simple-parse-error (_) nil))) (#+sbcl sb-int:simple-parse-error
#-sbcl parse-error (_) (declare (ignore _)) nil)))
;;; ;;;
;;; Function utilities ;;; Function utilities

View file

@ -10,7 +10,7 @@
;; String handling ;; String handling
#:+dottime-format+ #:format-dottime #:+dottime-format+ #:format-dottime
#:try-parse-integer #:try-parse-integer #:format-dottime-offset
;; Function utilities ;; Function utilities
#:partial)) #:partial))

197
third_party/tvl/net/alcoholic_jwt/Cargo.lock generated vendored Normal file
View file

@ -0,0 +1,197 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "alcoholic_jwt"
version = "4091.0.0"
dependencies = [
"base64",
"openssl",
"serde",
"serde_derive",
"serde_json",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "itoa"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "libc"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
[[package]]
name = "once_cell"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "openssl"
version = "0.10.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-sys"
version = "0.9.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "pkg-config"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "proc-macro2"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
name = "serde"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
[[package]]
name = "serde_derive"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a07e33e919ebcd69113d5be0e4d70c5707004ff45188910106854f38b960df4a"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "unicode-xid"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"

View file

@ -1,15 +1,16 @@
[package] [package]
name = "alcoholic_jwt" name = "alcoholic_jwt"
description = "Library for validation of RS256 JWTs" description = "Library for validation of RS256 JWTs"
version = "1.0.0" version = "4091.0.0"
authors = ["Vincent Ambo <vincent@aprila.no>"] authors = ["Vincent Ambo <tazjin@tvl.su>"]
keywords = ["jwt", "token", "jwks"] keywords = ["jwt", "token", "jwks"]
categories = ["authentication"] categories = ["authentication"]
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
repository = "https://github.com/aprilabank/alcoholic_jwt" homepage = "https://code.tvl.fyi/about/net/alcoholic_jwt"
repository = "https://code.tvl.fyi/depot.git:/net/alcoholic_jwt.git"
[dependencies] [dependencies]
base64 = "0.10" base64 = "0.13"
openssl = "0.10" openssl = "0.10"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"

View file

@ -1,8 +1,6 @@
alcoholic_jwt alcoholic_jwt
============= =============
[![Build Status](https://travis-ci.org/aprilabank/alcoholic_jwt.svg?branch=master)](https://travis-ci.org/aprilabank/alcoholic_jwt)
This is a library for **validation** of **RS256** JWTs using keys from This is a library for **validation** of **RS256** JWTs using keys from
a JWKS. Nothing more, nothing less. a JWKS. Nothing more, nothing less.
@ -56,7 +54,19 @@ This library aims to only use trustworthy off-the-shelf components to
do the work. Cryptographic operations are provided by the `openssl` do the work. Cryptographic operations are provided by the `openssl`
crate, JSON-serialisation is provided by `serde_json`. crate, JSON-serialisation is provided by `serde_json`.
## Contributing
This project is developed in the [TVL monorepo][depot]. To work on it,
you can either use a local clone of the entire repository or clone
just the `alcoholic_jwt` subtree:
https://code.tvl.fyi/depot.git:/net/alcoholic_jwt.git
Please follow the TVL [contribution guidelines][contributing].
[Google]: https://www.google.com/ [Google]: https://www.google.com/
[Aprila]: https://www.aprila.no/ [Aprila]: https://www.aprila.no/
[JWKS]: https://tools.ietf.org/html/rfc7517 [JWKS]: https://tools.ietf.org/html/rfc7517
[`kid` claim]: https://tools.ietf.org/html/rfc7515#section-4.1.4 [`kid` claim]: https://tools.ietf.org/html/rfc7515#section-4.1.4
[depot]: https://code.tvl.fyi/
[contributing]: https://code.tvl.fyi/about/docs/CONTRIBUTING.md

View file

@ -0,0 +1,9 @@
{ depot, pkgs, ... }:
depot.third_party.naersk.buildPackage {
src = ./.;
buildInputs = with pkgs; [
openssl
pkgconfig
];
}

View file

@ -1,4 +1,4 @@
// Copyright (C) 2018 Aprila Bank ASA // Copyright (C) 2019-2022 The TVL Community
// //
// alcoholic_jwt is free software: you can redistribute it and/or // alcoholic_jwt is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as // modify it under the terms of the GNU General Public License as
@ -67,23 +67,26 @@
//! //!
//! [JWKS]: https://tools.ietf.org/html/rfc7517 //! [JWKS]: https://tools.ietf.org/html/rfc7517
#[macro_use] extern crate serde_derive; #[macro_use]
extern crate serde_derive;
extern crate base64; extern crate base64;
extern crate openssl; extern crate openssl;
extern crate serde; extern crate serde;
extern crate serde_json; extern crate serde_json;
use base64::{URL_SAFE_NO_PAD, Config, DecodeError}; use base64::{Config, DecodeError, URL_SAFE_NO_PAD};
use openssl::bn::BigNum; use openssl::bn::BigNum;
use openssl::error::ErrorStack; use openssl::error::ErrorStack;
use openssl::hash::MessageDigest; use openssl::hash::MessageDigest;
use openssl::pkey::{Public, PKey}; use openssl::pkey::{PKey, Public};
use openssl::rsa::Rsa; use openssl::rsa::Rsa;
use openssl::sign::Verifier; use openssl::sign::Verifier;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde_json::Value; use serde_json::Value;
use std::time::{UNIX_EPOCH, Duration, SystemTime}; use std::error::Error;
use std::fmt::{self, Display};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -101,12 +104,16 @@ fn jwt_forgiving() -> Config {
/// JWT algorithm used. The only supported algorithm is currently /// JWT algorithm used. The only supported algorithm is currently
/// RS256. /// RS256.
#[derive(Clone, Deserialize, Debug)] #[derive(Clone, Deserialize, Debug)]
enum KeyAlgorithm { RS256 } enum KeyAlgorithm {
RS256,
}
/// Type of key contained in a JWT. The only supported key type is /// Type of key contained in a JWT. The only supported key type is
/// currently RSA. /// currently RSA.
#[derive(Clone, Deserialize, Debug)] #[derive(Clone, Deserialize, Debug)]
enum KeyType { RSA } enum KeyType {
RSA,
}
/// Representation of a single JSON Web Key. See [RFC /// Representation of a single JSON Web Key. See [RFC
/// 7517](https://tools.ietf.org/html/rfc7517#section-4). /// 7517](https://tools.ietf.org/html/rfc7517#section-4).
@ -146,7 +153,7 @@ impl JWKS {
/// Representation of an undecoded JSON Web Token. See [RFC /// Representation of an undecoded JSON Web Token. See [RFC
/// 7519](https://tools.ietf.org/html/rfc7519). /// 7519](https://tools.ietf.org/html/rfc7519).
struct JWT<'a> (&'a str); struct JWT<'a>(&'a str);
/// Representation of a decoded and validated JSON Web Token. /// Representation of a decoded and validated JSON Web Token.
/// ///
@ -214,18 +221,56 @@ pub enum ValidationError {
InvalidClaims(Vec<&'static str>), InvalidClaims(Vec<&'static str>),
} }
impl Error for ValidationError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ValidationError::InvalidBase64(e) => Some(e),
ValidationError::OpenSSL(e) => Some(e),
ValidationError::JSON(e) => Some(e),
ValidationError::InvalidComponents
| ValidationError::InvalidJWK
| ValidationError::InvalidSignature
| ValidationError::InvalidClaims(_) => None,
}
}
}
impl Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValidationError::InvalidComponents => {
f.write_str("Invalid number of token components in JWT")
}
ValidationError::InvalidBase64(_) => f.write_str("Invalid Base64 encoding in JWT"),
ValidationError::InvalidJWK => f.write_str("JWK decoding failed"),
ValidationError::InvalidSignature => f.write_str("JWT signature validation failed"),
ValidationError::OpenSSL(e) => write!(f, "SSL error: {}", e),
ValidationError::JSON(e) => write!(f, "JSON error: {}", e),
ValidationError::InvalidClaims(errs) => {
write!(f, "Invalid claims: {}", errs.join(", "))
}
}
}
}
type JWTResult<T> = Result<T, ValidationError>; type JWTResult<T> = Result<T, ValidationError>;
impl From<ErrorStack> for ValidationError { impl From<ErrorStack> for ValidationError {
fn from(err: ErrorStack) -> Self { ValidationError::OpenSSL(err) } fn from(err: ErrorStack) -> Self {
ValidationError::OpenSSL(err)
}
} }
impl From<serde_json::Error> for ValidationError { impl From<serde_json::Error> for ValidationError {
fn from(err: serde_json::Error) -> Self { ValidationError::JSON(err) } fn from(err: serde_json::Error) -> Self {
ValidationError::JSON(err)
}
} }
impl From<DecodeError> for ValidationError { impl From<DecodeError> for ValidationError {
fn from(err: DecodeError) -> Self { ValidationError::InvalidBase64(err) } fn from(err: DecodeError) -> Self {
ValidationError::InvalidBase64(err)
}
} }
/// Attempt to extract the `kid`-claim out of a JWT's header claims. /// Attempt to extract the `kid`-claim out of a JWT's header claims.
@ -266,9 +311,7 @@ pub fn token_kid(token: &str) -> JWTResult<Option<String>> {
/// ///
/// It is the user's task to ensure that the correct JWK is passed in /// It is the user's task to ensure that the correct JWK is passed in
/// for validation. /// for validation.
pub fn validate(token: &str, pub fn validate(token: &str, jwk: &JWK, validations: Vec<Validation>) -> JWTResult<ValidJWT> {
jwk: &JWK,
validations: Vec<Validation>) -> JWTResult<ValidJWT> {
let jwt = JWT(token); let jwt = JWT(token);
let public_key = public_key_from_jwk(&jwk)?; let public_key = public_key_from_jwk(&jwk)?;
validate_jwt_signature(&jwt, public_key)?; validate_jwt_signature(&jwt, public_key)?;
@ -279,7 +322,7 @@ pub fn validate(token: &str,
if parts.len() != 3 { if parts.len() != 3 {
// This is unlikely considering that validation has already // This is unlikely considering that validation has already
// been performed at this point, but better safe than sorry. // been performed at this point, but better safe than sorry.
return Err(ValidationError::InvalidComponents) return Err(ValidationError::InvalidComponents);
} }
// Perform claim validations before constructing the valid token: // Perform claim validations before constructing the valid token:
@ -351,7 +394,7 @@ fn validate_jwt_signature(jwt: &JWT, key: Rsa<Public>) -> JWTResult<()> {
verifier.update(data.as_bytes())?; verifier.update(data.as_bytes())?;
match verifier.verify(&sig)? { match verifier.verify(&sig)? {
true => Ok(()), true => Ok(()),
false => Err(ValidationError::InvalidSignature), false => Err(ValidationError::InvalidSignature),
} }
} }
@ -362,7 +405,7 @@ fn validate_jwt_signature(jwt: &JWT, key: Rsa<Public>) -> JWTResult<()> {
#[serde(untagged)] #[serde(untagged)]
enum Audience { enum Audience {
Single(String), Single(String),
Multi(Vec<String>) Multi(Vec<String>),
} }
/// Internal helper struct for claims that are relevant for claim /// Internal helper struct for claims that are relevant for claim
@ -376,15 +419,14 @@ struct PartialClaims {
} }
/// Apply a single validation to the claim set of a token. /// Apply a single validation to the claim set of a token.
fn apply_validation(claims: &PartialClaims, fn apply_validation(claims: &PartialClaims, validation: Validation) -> Result<(), &'static str> {
validation: Validation) -> Result<(), &'static str> {
match validation { match validation {
// Validate that an 'iss' claim is present and matches the // Validate that an 'iss' claim is present and matches the
// supplied value. // supplied value.
Validation::Issuer(iss) => { Validation::Issuer(iss) => match claims.iss {
match claims.iss { None => Err("'iss' claim is missing"),
None => Err("'iss' claim is missing"), Some(ref claim) => {
Some(ref claim) => if *claim == iss { if *claim == iss {
Ok(()) Ok(())
} else { } else {
Err("'iss' claim does not match") Err("'iss' claim does not match")
@ -394,15 +436,17 @@ fn apply_validation(claims: &PartialClaims,
// Validate that an 'aud' claim is present and matches the // Validate that an 'aud' claim is present and matches the
// supplied value. // supplied value.
Validation::Audience(aud) => { Validation::Audience(aud) => match claims.aud {
match claims.aud { None => Err("'aud' claim is missing"),
None => Err("'aud' claim is missing"), Some(Audience::Single(ref claim)) => {
Some(Audience::Single(ref claim)) => if *claim == aud { if *claim == aud {
Ok(()) Ok(())
} else { } else {
Err("'aud' claim does not match") Err("'aud' claim does not match")
}, }
Some(Audience::Multi(ref claims)) => if claims.contains(&aud) { }
Some(Audience::Multi(ref claims)) => {
if claims.contains(&aud) {
Ok(()) Ok(())
} else { } else {
Err("'aud' claim does not match") Err("'aud' claim does not match")
@ -447,12 +491,12 @@ fn apply_validation(claims: &PartialClaims,
} }
/// Apply all requested validations to a partial claim set. /// Apply all requested validations to a partial claim set.
fn validate_claims(claims: PartialClaims, fn validate_claims(claims: PartialClaims, validations: Vec<Validation>) -> JWTResult<()> {
validations: Vec<Validation>) -> JWTResult<()> { let validation_errors: Vec<_> = validations
let validation_errors: Vec<_> = validations.into_iter() .into_iter()
.map(|v| apply_validation(&claims, v)) .map(|v| apply_validation(&claims, v))
.filter_map(|result| match result { .filter_map(|result| match result {
Ok(_) => None, Ok(_) => None,
Err(err) => Some(err), Err(err) => Some(err),
}) })
.collect(); .collect();

View file

@ -1,4 +1,4 @@
// Copyright (C) 2018 Aprila Bank ASA // Copyright (C) 2019-2022 The TVL Community
// //
// alcoholic_jwt is free software: you can redistribute it and/or // alcoholic_jwt is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as // modify it under the terms of the GNU General Public License as
@ -21,14 +21,19 @@ fn test_fragment_decoding() {
let bignum = decode_fragment(fragment).expect("Failed to decode fragment"); let bignum = decode_fragment(fragment).expect("Failed to decode fragment");
let expected = "19947781743618558124649689124245117083485690334420160711273532766920651190711502679542723943527557680293732686428091794139998732541701457212387600480039297092835433997837314251024513773285252960725418984381935183495143908023024822433135775773958512751261112853383693442999603704969543668619221464654540065497665889289271044207667765128672709218996183649696030570183970367596949687544839066873508106034650634722970893169823917299050098551447676778961773465887890052852528696684907153295689693676910831376066659456592813140662563597179711588277621736656871685099184755908108451080261403193680966083938080206832839445289"; let expected = "19947781743618558124649689124245117083485690334420160711273532766920651190711502679542723943527557680293732686428091794139998732541701457212387600480039297092835433997837314251024513773285252960725418984381935183495143908023024822433135775773958512751261112853383693442999603704969543668619221464654540065497665889289271044207667765128672709218996183649696030570183970367596949687544839066873508106034650634722970893169823917299050098551447676778961773465887890052852528696684907153295689693676910831376066659456592813140662563597179711588277621736656871685099184755908108451080261403193680966083938080206832839445289";
assert_eq!(expected, format!("{}", bignum), "Decoded fragment should match "); assert_eq!(
expected,
format!("{}", bignum),
"Decoded fragment should match "
);
} }
#[test] #[test]
fn test_decode_find_jwks() { fn test_decode_find_jwks() {
let json = "{\"keys\":[{\"kty\":\"RSA\",\"alg\":\"RS256\",\"use\":\"sig\",\"kid\":\"mUjI\\/rIMLLtung35BKZfdbrqtlEAAYJ4JX\\/SKvnLxJc=\",\"n\":\"ngRRjNbXgPW29oNtF0JgsyyfTwPyEL0u_X16s453X2AOc33XGFxVKLEQ7R_TiMenaKcr-tPifYqgps_deyi0XOr4I3SOdOMtAVKDZJCANe--CANOHZb-meIfjKhCHisvT90fm5Apd6qPRVsXsZ7A8pmClZHKM5fwZUkBv8NsPLm2Xy2sGOZIiwP_7z8m3j0abUzniPQsx2b3xcWimB9vRtshFHN1KgPUf1ALQ5xzLfJnlFkCxC7kmOxKC7_NpQ4kJR_DKzKFV_r3HxTqf-jddHcXIrrMcLQXCSyeLQtLaz7whQ4F-EfL42z4XgwPr4ji3sct2gWL13EqlbE5DDxLKQ\",\"e\":\"GK7oLCDbNPAF59LhvyseqcG04hDnPs58qGYolr_HHmaR4lulWJ90ozx6e4Ut363yKG2p9vwvivR5UIC-aLPtqT2qr-OtjhBFzUFVaMGZ6mPCvMKk0AgMYdOHvWTgBSqQtNJTvl1yYLnhcWyoE2fLQhoEbY9qUyCBCEOScXOZRDpnmBtz5I8q5yYMV6a920J24T_IYbxHgkGcEU2SGg-b1cOMD7Rja7vCfV---CQ2pR4leQ0jufzudDoe7z3mziJm-Ihcdrz2Ujy5kPEMdz6R55prJ-ENKrkD_X4u5aSlSRaetwmHS3oAVkjr1JwUNbqnpM-kOqieqHEp8LUmez-Znw\"}]}"; let json = "{\"keys\":[{\"kty\":\"RSA\",\"alg\":\"RS256\",\"use\":\"sig\",\"kid\":\"mUjI\\/rIMLLtung35BKZfdbrqtlEAAYJ4JX\\/SKvnLxJc=\",\"n\":\"ngRRjNbXgPW29oNtF0JgsyyfTwPyEL0u_X16s453X2AOc33XGFxVKLEQ7R_TiMenaKcr-tPifYqgps_deyi0XOr4I3SOdOMtAVKDZJCANe--CANOHZb-meIfjKhCHisvT90fm5Apd6qPRVsXsZ7A8pmClZHKM5fwZUkBv8NsPLm2Xy2sGOZIiwP_7z8m3j0abUzniPQsx2b3xcWimB9vRtshFHN1KgPUf1ALQ5xzLfJnlFkCxC7kmOxKC7_NpQ4kJR_DKzKFV_r3HxTqf-jddHcXIrrMcLQXCSyeLQtLaz7whQ4F-EfL42z4XgwPr4ji3sct2gWL13EqlbE5DDxLKQ\",\"e\":\"GK7oLCDbNPAF59LhvyseqcG04hDnPs58qGYolr_HHmaR4lulWJ90ozx6e4Ut363yKG2p9vwvivR5UIC-aLPtqT2qr-OtjhBFzUFVaMGZ6mPCvMKk0AgMYdOHvWTgBSqQtNJTvl1yYLnhcWyoE2fLQhoEbY9qUyCBCEOScXOZRDpnmBtz5I8q5yYMV6a920J24T_IYbxHgkGcEU2SGg-b1cOMD7Rja7vCfV---CQ2pR4leQ0jufzudDoe7z3mziJm-Ihcdrz2Ujy5kPEMdz6R55prJ-ENKrkD_X4u5aSlSRaetwmHS3oAVkjr1JwUNbqnpM-kOqieqHEp8LUmez-Znw\"}]}";
let jwks: JWKS = serde_json::from_str(json).expect("Failed to decode JWKS"); let jwks: JWKS = serde_json::from_str(json).expect("Failed to decode JWKS");
let jwk = jwks.find("mUjI/rIMLLtung35BKZfdbrqtlEAAYJ4JX/SKvnLxJc=") let jwk = jwks
.find("mUjI/rIMLLtung35BKZfdbrqtlEAAYJ4JX/SKvnLxJc=")
.expect("Failed to find required JWK"); .expect("Failed to find required JWK");
public_key_from_jwk(&jwk).expect("Failed to construct public key from JWK"); public_key_from_jwk(&jwk).expect("Failed to construct public key from JWK");
@ -39,18 +44,21 @@ fn test_token_kid() {
let jwt = "eyJraWQiOiI4ckRxOFB3MEZaY2FvWFdURVZRbzcrVGYyWXpTTDFmQnhOS1BDZWJhYWk0PSIsImFsZyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJpc3MiOiJhdXRoLnRlc3QuYXByaWxhLm5vIiwiaWF0IjoxNTM2MDUwNjkzLCJleHAiOjE1MzYwNTQyOTMsInN1YiI6IjQyIiwiZXh0Ijoic21va2V0ZXN0IiwicHJ2IjoiYXJpc3RpIiwic2NwIjoicHJvY2VzcyJ9.gOLsv98109qLkmRK6Dn7WWRHLW7o8W78WZcWvFZoxPLzVO0qvRXXRLYc9h5chpfvcWreLZ4f1cOdvxv31_qnCRSQQPOeQ7r7hj_sPEDzhKjk-q2aoNHaGGJg1vabI--9EFkFsGQfoS7UbMMssS44dgR68XEnKtjn0Vys-Vzbvz_CBSCH6yQhRLik2SU2jR2L7BoFvh4LGZ6EKoQWzm8Z-CHXLGLUs4Hp5aPhF46dGzgAzwlPFW4t9G4DciX1uB4vv1XnfTc5wqJch6ltjKMde1GZwLR757a8dJSBcmGWze3UNE2YH_VLD7NCwH2kkqr3gh8rn7lWKG4AUIYPxsw9CB"; let jwt = "eyJraWQiOiI4ckRxOFB3MEZaY2FvWFdURVZRbzcrVGYyWXpTTDFmQnhOS1BDZWJhYWk0PSIsImFsZyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJpc3MiOiJhdXRoLnRlc3QuYXByaWxhLm5vIiwiaWF0IjoxNTM2MDUwNjkzLCJleHAiOjE1MzYwNTQyOTMsInN1YiI6IjQyIiwiZXh0Ijoic21va2V0ZXN0IiwicHJ2IjoiYXJpc3RpIiwic2NwIjoicHJvY2VzcyJ9.gOLsv98109qLkmRK6Dn7WWRHLW7o8W78WZcWvFZoxPLzVO0qvRXXRLYc9h5chpfvcWreLZ4f1cOdvxv31_qnCRSQQPOeQ7r7hj_sPEDzhKjk-q2aoNHaGGJg1vabI--9EFkFsGQfoS7UbMMssS44dgR68XEnKtjn0Vys-Vzbvz_CBSCH6yQhRLik2SU2jR2L7BoFvh4LGZ6EKoQWzm8Z-CHXLGLUs4Hp5aPhF46dGzgAzwlPFW4t9G4DciX1uB4vv1XnfTc5wqJch6ltjKMde1GZwLR757a8dJSBcmGWze3UNE2YH_VLD7NCwH2kkqr3gh8rn7lWKG4AUIYPxsw9CB";
let kid = token_kid(&jwt).expect("Failed to extract token KID"); let kid = token_kid(&jwt).expect("Failed to extract token KID");
assert_eq!(Some("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=".into()), assert_eq!(
kid, "Extracted KID did not match expected KID"); Some("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=".into()),
kid,
"Extracted KID did not match expected KID"
);
} }
#[test] #[test]
fn test_validate_jwt() { fn test_validate_jwt() {
let jwks_json = "{\"keys\":[{\"kty\":\"RSA\",\"alg\":\"RS256\",\"use\":\"sig\",\"kid\":\"8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=\",\"n\":\"l4UTgk1zr-8C8utt0E57DtBV6qqAPWzVRrIuQS2j0_hp2CviaNl5XzGRDnB8gwk0Hx95YOhJupAe6RNq5ok3fDdxL7DLvppJNRLz3Ag9CsmDLcbXgNEQys33fBJaPw1v3GcaFC4tisU5p-o1f5RfWwvwdBtdBfGiwT1GRvbc5sFx6M4iYjg9uv1lNKW60PqSJW4iDYrfqzZmB0zF1SJ0BL_rnQZ1Wi_UkFmNe9arM8W9tI9T3Ie59HITFuyVSTCt6qQEtSfa1e5PiBaVuV3qoFI2jPBiVZQ6LPGBWEDyz4QtrHLdECPPoTF30NN6TSVwwlRbCuUUrdNdXdjYe2dMFQ\",\"e\":\"DhaD5zC7mzaDvHO192wKT_9sfsVmdy8w8T8C9VG17_b1jG2srd3cmc6Ycw-0blDf53Wrpi9-KGZXKHX6_uIuJK249WhkP7N1SHrTJxO0sUJ8AhK482PLF09Qtu6cUfJqY1X1y1S2vACJZItU4Vjr3YAfiVGQXeA8frAf7Sm4O1CBStCyg6yCcIbGojII0jfh2vSB-GD9ok1F69Nmk-R-bClyqMCV_Oq-5a0gqClVS8pDyGYMgKTww2RHgZaFSUcG13KeLMQsG2UOB2OjSC8FkOXK00NBlAjU3d0Vv-IamaLIszO7FQBY3Oh0uxNOvIE9ofQyCOpB-xIK6V9CTTphxw\"}]}"; let jwks_json = "{\"keys\":[{\"kty\":\"RSA\",\"alg\":\"RS256\",\"use\":\"sig\",\"kid\":\"8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=\",\"n\":\"l4UTgk1zr-8C8utt0E57DtBV6qqAPWzVRrIuQS2j0_hp2CviaNl5XzGRDnB8gwk0Hx95YOhJupAe6RNq5ok3fDdxL7DLvppJNRLz3Ag9CsmDLcbXgNEQys33fBJaPw1v3GcaFC4tisU5p-o1f5RfWwvwdBtdBfGiwT1GRvbc5sFx6M4iYjg9uv1lNKW60PqSJW4iDYrfqzZmB0zF1SJ0BL_rnQZ1Wi_UkFmNe9arM8W9tI9T3Ie59HITFuyVSTCt6qQEtSfa1e5PiBaVuV3qoFI2jPBiVZQ6LPGBWEDyz4QtrHLdECPPoTF30NN6TSVwwlRbCuUUrdNdXdjYe2dMFQ\",\"e\":\"DhaD5zC7mzaDvHO192wKT_9sfsVmdy8w8T8C9VG17_b1jG2srd3cmc6Ycw-0blDf53Wrpi9-KGZXKHX6_uIuJK249WhkP7N1SHrTJxO0sUJ8AhK482PLF09Qtu6cUfJqY1X1y1S2vACJZItU4Vjr3YAfiVGQXeA8frAf7Sm4O1CBStCyg6yCcIbGojII0jfh2vSB-GD9ok1F69Nmk-R-bClyqMCV_Oq-5a0gqClVS8pDyGYMgKTww2RHgZaFSUcG13KeLMQsG2UOB2OjSC8FkOXK00NBlAjU3d0Vv-IamaLIszO7FQBY3Oh0uxNOvIE9ofQyCOpB-xIK6V9CTTphxw\"}]}";
let jwks: JWKS = serde_json::from_str(jwks_json) let jwks: JWKS = serde_json::from_str(jwks_json).expect("Failed to decode JWKS");
.expect("Failed to decode JWKS");
let jwk = jwks.find("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=") let jwk = jwks
.find("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=")
.expect("Failed to find required JWK"); .expect("Failed to find required JWK");
let pkey = public_key_from_jwk(&jwk).expect("Failed to construct public key"); let pkey = public_key_from_jwk(&jwk).expect("Failed to construct public key");

180
third_party/tvl/net/crimp/Cargo.lock generated vendored Normal file
View file

@ -0,0 +1,180 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "crimp"
version = "0.2.2"
dependencies = [
"curl",
"serde",
"serde_json",
]
[[package]]
name = "curl"
version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f"
dependencies = [
"curl-sys",
"libc",
"openssl-probe",
"openssl-sys",
"schannel",
"socket2",
"winapi",
]
[[package]]
name = "curl-sys"
version = "0.4.55+curl-7.83.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762"
dependencies = [
"cc",
"libc",
"libz-sys",
"openssl-sys",
"pkg-config",
"vcpkg",
"winapi",
]
[[package]]
name = "itoa"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
[[package]]
name = "libz-sys"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e7e15d7610cce1d9752e137625f14e61a28cd45929b6e12e47b50fe154ee2e"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "pkg-config"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "ryu"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
name = "schannel"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
"lazy_static",
"winapi",
]
[[package]]
name = "serde"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
[[package]]
name = "serde_json"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "socket2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View file

@ -1,12 +1,14 @@
[package] [package]
name = "crimp" name = "crimp"
description = "Higher-level Rust API for cURL bindings" description = "Higher-level Rust API for cURL bindings"
version = "0.2.2" version = "4087.0.0"
authors = ["Vincent Ambo <mail@tazj.in>"] authors = ["Vincent Ambo <tazjin@tvl.su>"]
keywords = [ "http", "curl" ] keywords = [ "http", "curl" ]
categories = [ "api-bindings" ] categories = [ "api-bindings" ]
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
repository = "https://github.com/tazjin/crimp" homepage = "https://code.tvl.fyi/about/net/crimp"
repository = "https://code.tvl.fyi/depot.git:/net/crimp.git"
[features] [features]
default = [ "json" ] default = [ "json" ]

View file

@ -1,7 +1,6 @@
crimp crimp
===== =====
[![Build Status](https://travis-ci.org/tazjin/crimp.svg?branch=master)](https://travis-ci.org/tazjin/crimp)
[![](https://img.shields.io/crates/v/crimp.svg)](https://crates.io/crates/crimp) [![](https://img.shields.io/crates/v/crimp.svg)](https://crates.io/crates/crimp)
[![](https://docs.rs/crimp/badge.svg)](https://docs.rs/crimp) [![](https://docs.rs/crimp/badge.svg)](https://docs.rs/crimp)
@ -11,5 +10,17 @@ cURL.
The documentation for this crate is primarily in the [module The documentation for this crate is primarily in the [module
documentation][] documentation][]
-------
This project is developed in the [TVL monorepo][depot]. To work on it,
you can either use a local clone of the entire repository or clone
just the `crimp` subtree:
https://code.tvl.fyi/depot.git:/net/crimp.git
Please follow the TVL [contribution guidelines][contributing].
[Rust bindings]: https://docs.rs/curl [Rust bindings]: https://docs.rs/curl
[module documentation]: https://docs.rs/crimp [module documentation]: https://docs.rs/crimp
[depot]: https://code.tvl.fyi/
[contributing]: https://code.tvl.fyi/about/docs/CONTRIBUTING.md

9
third_party/tvl/net/crimp/default.nix vendored Normal file
View file

@ -0,0 +1,9 @@
{ depot, pkgs, ... }:
depot.third_party.naersk.buildPackage {
src = ./.;
buildInputs = with pkgs; [
openssl
pkgconfig
];
}

View file

@ -33,9 +33,12 @@
//! use crimp::Request; //! use crimp::Request;
//! //!
//! let response = Request::get("http://httpbin.org/get") //! let response = Request::get("http://httpbin.org/get")
//! .user_agent("crimp test suite").unwrap() //! .user_agent("crimp test suite")
//! .send().unwrap() //! .unwrap()
//! .as_string().unwrap(); //! .send()
//! .unwrap()
//! .as_string()
//! .unwrap();
//! //!
//! println!("Status: {}\nBody: {}", response.status, response.body); //! println!("Status: {}\nBody: {}", response.status, response.body);
//! # assert_eq!(response.status, 200); //! # assert_eq!(response.status, 200);
@ -54,10 +57,9 @@
//! //!
//! All optional features are enabled by default. //! All optional features are enabled by default.
//! //!
//! * `json`: Adds `Request::json` and `Response::as_json` methods //! * `json`: Adds `Request::json` and `Response::as_json` methods which can be used for convenient
//! which can be used for convenient serialisation of //! serialisation of request/response bodies using `serde_json`. This feature adds a dependency on
//! request/response bodies using `serde_json`. This feature adds a //! the `serde` and `serde_json` crates.
//! dependency on the `serde` and `serde_json` crates.
//! //!
//! ## Initialisation //! ## Initialisation
//! //!
@ -72,32 +74,42 @@
extern crate curl; extern crate curl;
#[cfg(feature = "json")] extern crate serde; #[cfg(feature = "json")]
#[cfg(feature = "json")] extern crate serde_json; extern crate serde;
#[cfg(feature = "json")]
extern crate serde_json;
pub use curl::init; pub use curl::init;
use curl::easy::{Auth, Easy, Form, List, Transfer, ReadError, WriteError}; use curl::easy::{Auth, Easy, Form, List, ReadError, Transfer, WriteError};
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
use std::string::{FromUtf8Error, ToString}; use std::string::{FromUtf8Error, ToString};
use std::time::Duration; use std::time::Duration;
#[cfg(feature = "json")] use serde::Serialize; #[cfg(feature = "json")]
#[cfg(feature = "json")] use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
#[cfg(feature = "json")]
use serde::Serialize;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
/// HTTP method to use for the request. /// HTTP method to use for the request.
enum Method { enum Method {
Get, Post, Put, Patch, Delete Get,
Post,
Put,
Patch,
Delete,
} }
/// Certificate types for client-certificate key pairs. /// Certificate types for client-certificate key pairs.
pub enum CertType { pub enum CertType {
P12, PEM, DER P12,
PEM,
DER,
} }
/// Builder structure for an HTTP request. /// Builder structure for an HTTP request.
@ -145,7 +157,7 @@ pub struct Response<T> {
pub body: T, pub body: T,
} }
impl <'a> Request<'a> { impl<'a> Request<'a> {
/// Initiate an HTTP request with the given method and URL. /// Initiate an HTTP request with the given method and URL.
fn new(method: Method, url: &'a str) -> Self { fn new(method: Method, url: &'a str) -> Self {
Request { Request {
@ -158,19 +170,29 @@ impl <'a> Request<'a> {
} }
/// Initiate a GET request with the given URL. /// Initiate a GET request with the given URL.
pub fn get(url: &'a str) -> Self { Request::new(Method::Get, url) } pub fn get(url: &'a str) -> Self {
Request::new(Method::Get, url)
}
/// Initiate a POST request with the given URL. /// Initiate a POST request with the given URL.
pub fn post(url: &'a str) -> Self { Request::new(Method::Post, url) } pub fn post(url: &'a str) -> Self {
Request::new(Method::Post, url)
}
/// Initiate a PUT request with the given URL. /// Initiate a PUT request with the given URL.
pub fn put(url: &'a str) -> Self { Request::new(Method::Put, url) } pub fn put(url: &'a str) -> Self {
Request::new(Method::Put, url)
}
/// Initiate a PATCH request with the given URL. /// Initiate a PATCH request with the given URL.
pub fn patch(url: &'a str) -> Self { Request::new(Method::Patch, url) } pub fn patch(url: &'a str) -> Self {
Request::new(Method::Patch, url)
}
/// Initiate a DELETE request with the given URL. /// Initiate a DELETE request with the given URL.
pub fn delete(url: &'a str) -> Self { Request::new(Method::Delete, url) } pub fn delete(url: &'a str) -> Self {
Request::new(Method::Delete, url)
}
/// Add an HTTP header to a request. /// Add an HTTP header to a request.
pub fn header(mut self, k: &str, v: &str) -> Result<Self, curl::Error> { pub fn header(mut self, k: &str, v: &str) -> Result<Self, curl::Error> {
@ -188,7 +210,8 @@ impl <'a> Request<'a> {
/// Set the `Authorization` header to a `Bearer` value with the /// Set the `Authorization` header to a `Bearer` value with the
/// supplied token. /// supplied token.
pub fn bearer_auth(mut self, token: &str) -> Result<Self, curl::Error> { pub fn bearer_auth(mut self, token: &str) -> Result<Self, curl::Error> {
self.headers.append(&format!("Authorization: Bearer {}", token))?; self.headers
.append(&format!("Authorization: Bearer {}", token))?;
Ok(self) Ok(self)
} }
@ -212,8 +235,11 @@ impl <'a> Request<'a> {
/// Consult the documentation for the `ssl_cert` and `ssl_key` /// Consult the documentation for the `ssl_cert` and `ssl_key`
/// functions in `curl::easy::Easy2` for details on supported /// functions in `curl::easy::Easy2` for details on supported
/// formats and defaults. /// formats and defaults.
pub fn tls_client_cert<P: AsRef<Path>>(mut self, cert_type: CertType, cert: P) pub fn tls_client_cert<P: AsRef<Path>>(
-> Result<Self, curl::Error> { mut self,
cert_type: CertType,
cert: P,
) -> Result<Self, curl::Error> {
self.handle.ssl_cert(cert)?; self.handle.ssl_cert(cert)?;
self.handle.ssl_cert_type(match cert_type { self.handle.ssl_cert_type(match cert_type {
CertType::P12 => "P12", CertType::P12 => "P12",
@ -262,13 +288,17 @@ impl <'a> Request<'a> {
/// ``` /// ```
/// # use crimp::Request; /// # use crimp::Request;
/// let response = Request::get("https://httpbin.org/get") /// let response = Request::get("https://httpbin.org/get")
/// .with_handle(|mut handle| handle.referer("Example-Referer")).unwrap() /// .with_handle(|mut handle| handle.referer("Example-Referer"))
/// .send().unwrap(); /// .unwrap()
/// .send()
/// .unwrap();
/// # /// #
/// # assert!(response.is_success()); /// # assert!(response.is_success());
/// ``` /// ```
pub fn with_handle<F>(mut self, function: F) -> Result<Self, curl::Error> pub fn with_handle<F>(mut self, function: F) -> Result<Self, curl::Error>
where F: FnOnce(&mut Easy) -> Result<(), curl::Error> { where
F: FnOnce(&mut Easy) -> Result<(), curl::Error>,
{
function(&mut self.handle)?; function(&mut self.handle)?;
Ok(self) Ok(self)
} }
@ -293,12 +323,15 @@ impl <'a> Request<'a> {
/// let mut form = Form::new(); /// let mut form = Form::new();
/// form.part("some-name") /// form.part("some-name")
/// .contents("some-data".as_bytes()) /// .contents("some-data".as_bytes())
/// .add().unwrap(); /// .add()
/// .unwrap();
/// ///
/// let response = Request::post("https://httpbin.org/post") /// let response = Request::post("https://httpbin.org/post")
/// .user_agent("crimp test suite").unwrap() /// .user_agent("crimp test suite")
/// .unwrap()
/// .form(form) /// .form(form)
/// .send().unwrap(); /// .send()
/// .unwrap();
/// # /// #
/// # assert_eq!(200, response.status, "form POST should succeed"); /// # assert_eq!(200, response.status, "form POST should succeed");
/// # assert_eq!( /// # assert_eq!(
@ -330,10 +363,10 @@ impl <'a> Request<'a> {
self.handle.url(self.url)?; self.handle.url(self.url)?;
match self.method { match self.method {
Method::Get => self.handle.get(true)?, Method::Get => self.handle.get(true)?,
Method::Post => self.handle.post(true)?, Method::Post => self.handle.post(true)?,
Method::Put => self.handle.put(true)?, Method::Put => self.handle.put(true)?,
Method::Patch => self.handle.custom_request("PATCH")?, Method::Patch => self.handle.custom_request("PATCH")?,
Method::Delete => self.handle.custom_request("DELETE")?, Method::Delete => self.handle.custom_request("DELETE")?,
} }
@ -351,21 +384,22 @@ impl <'a> Request<'a> {
// Optionally set content type if a body payload is configured // Optionally set content type if a body payload is configured
// and configure the expected body size (or form payload). // and configure the expected body size (or form payload).
match self.body { match self.body {
Body::Bytes { content_type, data } => { Body::Bytes { content_type, data } => {
self.handle.post_field_size(data.len() as u64)?; self.handle.post_field_size(data.len() as u64)?;
self.headers.append(&format!("Content-Type: {}", content_type))?; self.headers
}, .append(&format!("Content-Type: {}", content_type))?;
}
#[cfg(feature = "json")] #[cfg(feature = "json")]
Body::Json(ref data) => { Body::Json(ref data) => {
self.handle.post_field_size(data.len() as u64)?; self.handle.post_field_size(data.len() as u64)?;
self.headers.append("Content-Type: application/json")?; self.headers.append("Content-Type: application/json")?;
}, }
// Do not set content-type header at all if there is no // Do not set content-type header at all if there is no
// body, or if the form handler was invoked above. // body, or if the form handler was invoked above.
_ => (), _ => (),
}; };
// Configure headers on the request: // Configure headers on the request:
@ -407,9 +441,7 @@ impl <'a> Request<'a> {
return true; return true;
} }
headers.insert( headers.insert(split[0].trim().to_string(), split[1].trim().to_string());
split[0].trim().to_string(), split[1].trim().to_string()
);
true true
})?; })?;
@ -427,7 +459,7 @@ impl <'a> Request<'a> {
Ok(Response { Ok(Response {
status: self.handle.response_code()?, status: self.handle.response_code()?,
headers, headers,
body body,
}) })
} }
} }
@ -438,13 +470,14 @@ impl <'a> Request<'a> {
/// ///
/// As we manually set the expected upload size, cURL will call the /// As we manually set the expected upload size, cURL will call the
/// read callback repeatedly until it has all the data it needs. /// read callback repeatedly until it has all the data it needs.
fn chunked_read_function<'easy, 'data>(transfer: &mut Transfer<'easy, 'data>, fn chunked_read_function<'easy, 'data>(
data: &'data [u8]) -> Result<(), curl::Error> { transfer: &mut Transfer<'easy, 'data>,
data: &'data [u8],
) -> Result<(), curl::Error> {
let mut data = data; let mut data = data;
transfer.read_function(move |mut into| { transfer.read_function(move |mut into| {
let written = into.write(data) let written = into.write(data).map_err(|_| ReadError::Abort)?;
.map_err(|_| ReadError::Abort)?;
data = &data[written..]; data = &data[written..];
@ -452,7 +485,7 @@ fn chunked_read_function<'easy, 'data>(transfer: &mut Transfer<'easy, 'data>,
}) })
} }
impl <T> Response<T> { impl<T> Response<T> {
/// Check whether the status code of this HTTP response is a /// Check whether the status code of this HTTP response is a
/// success (i.e. in the 200-299 range). /// success (i.e. in the 200-299 range).
pub fn is_success(&self) -> bool { pub fn is_success(&self) -> bool {
@ -466,9 +499,11 @@ impl <T> Response<T> {
/// This function exists for convenience to avoid having to write /// This function exists for convenience to avoid having to write
/// repetitive `if !response.is_success() { ... }` blocks. /// repetitive `if !response.is_success() { ... }` blocks.
pub fn error_for_status<F, E>(self, closure: F) -> Result<Self, E> pub fn error_for_status<F, E>(self, closure: F) -> Result<Self, E>
where F: FnOnce(Self) -> E { where
F: FnOnce(Self) -> E,
{
if !self.is_success() { if !self.is_success() {
return Err(closure(self)) return Err(closure(self));
} }
Ok(self) Ok(self)

View file

@ -6,7 +6,7 @@
// docker run --rm -p 4662:80 kennethreitz/httpbin // docker run --rm -p 4662:80 kennethreitz/httpbin
use super::*; use super::*;
use serde_json::{Value, json}; use serde_json::{json, Value};
// These tests check whether the correct HTTP method is used in the // These tests check whether the correct HTTP method is used in the
// requests. // requests.
@ -14,7 +14,8 @@ use serde_json::{Value, json};
#[test] #[test]
fn test_http_get() { fn test_http_get() {
let resp = Request::get("http://127.0.0.1:4662/get") let resp = Request::get("http://127.0.0.1:4662/get")
.send().expect("failed to send request"); .send()
.expect("failed to send request");
assert!(resp.is_success(), "request should have succeeded"); assert!(resp.is_success(), "request should have succeeded");
} }
@ -22,7 +23,8 @@ fn test_http_get() {
#[test] #[test]
fn test_http_delete() { fn test_http_delete() {
let resp = Request::delete("http://127.0.0.1:4662/delete") let resp = Request::delete("http://127.0.0.1:4662/delete")
.send().expect("failed to send request"); .send()
.expect("failed to send request");
assert_eq!(200, resp.status, "response status should be 200 OK"); assert_eq!(200, resp.status, "response status should be 200 OK");
} }
@ -30,7 +32,8 @@ fn test_http_delete() {
#[test] #[test]
fn test_http_put() { fn test_http_put() {
let resp = Request::put("http://127.0.0.1:4662/put") let resp = Request::put("http://127.0.0.1:4662/put")
.send().expect("failed to send request"); .send()
.expect("failed to send request");
assert_eq!(200, resp.status, "response status should be 200 OK"); assert_eq!(200, resp.status, "response status should be 200 OK");
} }
@ -38,7 +41,8 @@ fn test_http_put() {
#[test] #[test]
fn test_http_patch() { fn test_http_patch() {
let resp = Request::patch("http://127.0.0.1:4662/patch") let resp = Request::patch("http://127.0.0.1:4662/patch")
.send().expect("failed to send request"); .send()
.expect("failed to send request");
assert_eq!(200, resp.status, "response status should be 200 OK"); assert_eq!(200, resp.status, "response status should be 200 OK");
} }
@ -50,18 +54,25 @@ fn test_http_patch() {
fn test_http_post() { fn test_http_post() {
let body = "test body"; let body = "test body";
let response = Request::post("http://127.0.0.1:4662/post") let response = Request::post("http://127.0.0.1:4662/post")
.user_agent("crimp test suite").expect("failed to set user-agent") .user_agent("crimp test suite")
.timeout(Duration::from_secs(5)).expect("failed to set request timeout") .expect("failed to set user-agent")
.timeout(Duration::from_secs(5))
.expect("failed to set request timeout")
.body("text/plain", &body.as_bytes()) .body("text/plain", &body.as_bytes())
.send().expect("failed to send request") .send()
.as_json::<Value>().expect("failed to deserialize response"); .expect("failed to send request")
.as_json::<Value>()
.expect("failed to deserialize response");
let data = response.body; let data = response.body;
assert_eq!(200, response.status, "response status should be 200 OK"); assert_eq!(200, response.status, "response status should be 200 OK");
assert_eq!(data.get("data").unwrap(), &json!("test body"), assert_eq!(
"test body should have been POSTed"); data.get("data").unwrap(),
&json!("test body"),
"test body should have been POSTed"
);
assert_eq!( assert_eq!(
data.get("headers").unwrap().get("Content-Type").unwrap(), data.get("headers").unwrap().get("Content-Type").unwrap(),
@ -70,26 +81,34 @@ fn test_http_post() {
); );
} }
#[cfg(feature = "json")] #[test] #[cfg(feature = "json")]
#[test]
fn test_http_post_json() { fn test_http_post_json() {
let body = json!({ let body = json!({
"purpose": "testing!" "purpose": "testing!"
}); });
let response = Request::post("http://127.0.0.1:4662/post") let response = Request::post("http://127.0.0.1:4662/post")
.user_agent("crimp test suite").expect("failed to set user-agent") .user_agent("crimp test suite")
.timeout(Duration::from_secs(5)).expect("failed to set request timeout") .expect("failed to set user-agent")
.json(&body).expect("request serialization failed") .timeout(Duration::from_secs(5))
.send().expect("failed to send request") .expect("failed to set request timeout")
.as_json::<Value>().expect("failed to deserialize response"); .json(&body)
.expect("request serialization failed")
.send()
.expect("failed to send request")
.as_json::<Value>()
.expect("failed to deserialize response");
let data = response.body; let data = response.body;
assert_eq!(200, response.status, "response status should be 200 OK"); assert_eq!(200, response.status, "response status should be 200 OK");
assert_eq!(data.get("json").unwrap(), &body, assert_eq!(
"test body should have been POSTed"); data.get("json").unwrap(),
&body,
"test body should have been POSTed"
);
assert_eq!( assert_eq!(
data.get("headers").unwrap().get("Content-Type").unwrap(), data.get("headers").unwrap().get("Content-Type").unwrap(),
@ -104,8 +123,10 @@ fn test_http_post_json() {
#[test] #[test]
fn test_bearer_auth() { fn test_bearer_auth() {
let response = Request::get("http://127.0.0.1:4662/bearer") let response = Request::get("http://127.0.0.1:4662/bearer")
.bearer_auth("some-token").expect("failed to set auth header") .bearer_auth("some-token")
.send().expect("failed to send request"); .expect("failed to set auth header")
.send()
.expect("failed to send request");
assert!(response.is_success(), "authorized request should succeed"); assert!(response.is_success(), "authorized request should succeed");
} }
@ -115,8 +136,10 @@ fn test_basic_auth() {
let request = Request::get("http://127.0.0.1:4662/basic-auth/alan_watts/oneness"); let request = Request::get("http://127.0.0.1:4662/basic-auth/alan_watts/oneness");
let response = request let response = request
.basic_auth("alan_watts", "oneness").expect("failed to set auth header") .basic_auth("alan_watts", "oneness")
.send().expect("failed to send request"); .expect("failed to set auth header")
.send()
.expect("failed to send request");
assert!(response.is_success(), "authorized request should succeed"); assert!(response.is_success(), "authorized request should succeed");
} }
@ -129,14 +152,20 @@ fn test_large_body() {
let resp = Request::post("http://127.0.0.1:4662/post") let resp = Request::post("http://127.0.0.1:4662/post")
.body("application/octet-stream", &[0; BODY_SIZE]) .body("application/octet-stream", &[0; BODY_SIZE])
.send().expect("sending request") .send()
.as_json::<Value>().expect("JSON deserialisation"); .expect("sending request")
.as_json::<Value>()
.expect("JSON deserialisation");
// httpbin returns the uploaded data as a string in the `data` // httpbin returns the uploaded data as a string in the `data`
// field. // field.
let data = resp.body.get("data").unwrap().as_str().unwrap(); let data = resp.body.get("data").unwrap().as_str().unwrap();
assert_eq!(BODY_SIZE, data.len(), "uploaded data length should be correct"); assert_eq!(
BODY_SIZE,
data.len(),
"uploaded data length should be correct"
);
} }
// Tests for various other features. // Tests for various other features.
@ -144,9 +173,13 @@ fn test_large_body() {
#[test] #[test]
fn test_error_for_status() { fn test_error_for_status() {
let response = Request::get("http://127.0.0.1:4662/patch") let response = Request::get("http://127.0.0.1:4662/patch")
.send().expect("failed to send request") .send()
.expect("failed to send request")
.error_for_status(|resp| format!("Response error code: {}", resp.status)); .error_for_status(|resp| format!("Response error code: {}", resp.status));
assert_eq!(Err("Response error code: 405".into()), response, assert_eq!(
"returned error should be converted into Result::Err"); Err("Response error code: 405".into()),
response,
"returned error should be converted into Result::Err"
);
} }

View file

@ -10,7 +10,7 @@
# with `binify { exe = …; name = "hello" }`. # with `binify { exe = …; name = "hello" }`.
{ exe, name }: { exe, name }:
pkgs.runCommandLocal "${name}-bin" {} '' pkgs.runCommandLocal "${name}-bin" { } ''
mkdir -p $out/bin mkdir -p $out/bin
ln -sT ${lib.escapeShellArg exe} $out/bin/${lib.escapeShellArg name} ln -sT ${lib.escapeShellArg exe} $out/bin/${lib.escapeShellArg name}
'' ''

View file

@ -3,7 +3,7 @@
{ depot, pkgs, ... }: { depot, pkgs, ... }:
pkgs.writeShellScriptBin "ci-buf-check" '' pkgs.writeShellScriptBin "ci-buf-check" ''
${depot.third_party.bufbuild}/bin/buf check lint --input "${depot.depotPath}" ${depot.third_party.bufbuild}/bin/buf check lint --input .
# Report-only # Report-only
${depot.third_party.bufbuild}/bin/buf check breaking --input "${depot.depotPath}" --against-input "${depot.depotPath}/.git#branch=canon" || true ${depot.third_party.bufbuild}/bin/buf check breaking --input "." --against-input "./.git#branch=canon" || true
'' ''

View file

@ -4,9 +4,9 @@
# buildGo provides Nix functions to build Go packages in the style of Bazel's # buildGo provides Nix functions to build Go packages in the style of Bazel's
# rules_go. # rules_go.
{ pkgs ? import <nixpkgs> {} { pkgs ? import <nixpkgs> { }
, gopkgs , ...
, ... }: }:
let let
inherit (builtins) inherit (builtins)
@ -22,7 +22,7 @@ let
replaceStrings replaceStrings
toString; toString;
inherit (pkgs) lib go runCommand fetchFromGitHub protobuf symlinkJoin removeReferencesTo; inherit (pkgs) lib go runCommand fetchFromGitHub protobuf symlinkJoin;
# Helpers for low-level Go compiler invocations # Helpers for low-level Go compiler invocations
spaceOut = lib.concatStringsSep " "; spaceOut = lib.concatStringsSep " ";
@ -41,9 +41,7 @@ let
xFlags = x_defs: spaceOut (map (k: "-X ${k}=${x_defs."${k}"}") (attrNames x_defs)); xFlags = x_defs: spaceOut (map (k: "-X ${k}=${x_defs."${k}"}") (attrNames x_defs));
pathToName = p: replaceStrings ["/"] ["_"] (toString p); pathToName = p: replaceStrings [ "/" ] [ "_" ] (toString p);
removeRefs = refs: "${removeReferencesTo}/bin/remove-references-to ${lib.concatMapStrings (ref: " -t ${ref}") ([ go ] ++ refs)}";
# Add an `overrideGo` attribute to a function result that works # Add an `overrideGo` attribute to a function result that works
# similar to `overrideAttrs`, but is used specifically for the # similar to `overrideAttrs`, but is used specifically for the
@ -55,51 +53,52 @@ let
# High-level build functions # High-level build functions
# Build a Go program out of the specified files and dependencies. # Build a Go program out of the specified files and dependencies.
program = { name, srcs, deps ? [], x_defs ? {} }: program = { name, srcs, deps ? [ ], x_defs ? { } }:
let uniqueDeps = allDeps (map (d: d.gopkg) deps); let uniqueDeps = allDeps (map (d: d.gopkg) deps);
in runCommand name {} '' in runCommand name { } ''
${go}/bin/go tool compile -o ${name}.a -trimpath="$PWD;${go}" ${includeSources uniqueDeps} ${spaceOut srcs} ${go}/bin/go tool compile -o ${name}.a -trimpath=$PWD -trimpath=${go} ${includeSources uniqueDeps} ${spaceOut srcs}
mkdir -p $out/bin mkdir -p $out/bin
export GOROOT_FINAL=go export GOROOT_FINAL=go
${go}/bin/go tool link -o $out/bin/${name} -buildid nix ${xFlags x_defs} ${includeLibs uniqueDeps} ${name}.a ${go}/bin/go tool link -o $out/bin/${name} -buildid nix ${xFlags x_defs} ${includeLibs uniqueDeps} ${name}.a
${removeRefs srcs} $out/bin/${name} '';
'';
# Build a Go library assembled out of the specified files. # Build a Go library assembled out of the specified files.
# #
# This outputs both the sources and compiled binary, as both are # This outputs both the sources and compiled binary, as both are
# needed when downstream packages depend on it. # needed when downstream packages depend on it.
package = { name, srcs, deps ? [], path ? name, sfiles ? [] }: package = { name, srcs, deps ? [ ], path ? name, sfiles ? [ ] }:
let let
uniqueDeps = allDeps (map (d: d.gopkg) deps); uniqueDeps = allDeps (map (d: d.gopkg) deps);
# The build steps below need to be executed conditionally for Go # The build steps below need to be executed conditionally for Go
# assembly if the analyser detected any *.s files. # assembly if the analyser detected any *.s files.
# #
# This is required for several popular packages (e.g. x/sys). # This is required for several popular packages (e.g. x/sys).
ifAsm = do: lib.optionalString (sfiles != []) do; ifAsm = do: lib.optionalString (sfiles != [ ]) do;
asmBuild = ifAsm '' asmBuild = ifAsm ''
${go}/bin/go tool asm -trimpath $PWD -I $PWD -I ${go}/share/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -gensymabis -o ./symabis ${spaceOut sfiles} ${go}/bin/go tool asm -trimpath $PWD -I $PWD -I ${go}/share/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -gensymabis -o ./symabis ${spaceOut sfiles}
${go}/bin/go tool asm -trimpath $PWD -I $PWD -I ${go}/share/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -o ./asm.o ${spaceOut sfiles} ${go}/bin/go tool asm -trimpath $PWD -I $PWD -I ${go}/share/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -o ./asm.o ${spaceOut sfiles}
''; '';
asmLink = ifAsm "-symabis ./symabis -asmhdr $out/go_asm.h"; asmLink = ifAsm "-symabis ./symabis -asmhdr $out/go_asm.h";
asmPack = ifAsm '' asmPack = ifAsm ''
${go}/bin/go tool pack r $out/${path}.a ./asm.o ${go}/bin/go tool pack r $out/${path}.a ./asm.o
''; '';
gopkg = (runCommand "golib-${name}" {} '' gopkg = (runCommand "golib-${name}" { } ''
mkdir -p $out/${path} mkdir -p $out/${path}
${srcList path (map (s: "${s}") srcs)} ${srcList path (map (s: "${s}") srcs)}
${asmBuild} ${asmBuild}
${go}/bin/go tool compile -pack ${asmLink} -o $out/${path}.a -trimpath="$PWD;${go}" -p ${path} ${includeSources uniqueDeps} ${spaceOut srcs} ${go}/bin/go tool compile -pack ${asmLink} -o $out/${path}.a -trimpath=$PWD -trimpath=${go} -p ${path} ${includeSources uniqueDeps} ${spaceOut srcs}
${asmPack} ${asmPack}
${removeRefs srcs} $out/${path}.a '').overrideAttrs (_: {
'') // { passthru = {
inherit gopkg; inherit gopkg;
goDeps = uniqueDeps; goDeps = uniqueDeps;
goImportPath = path; goImportPath = path;
}; };
in gopkg; });
in
gopkg;
# Build a tree of Go libraries out of an external Go source # Build a tree of Go libraries out of an external Go source
# directory that follows the standard Go layout and was not built # directory that follows the standard Go layout and was not built
@ -111,50 +110,31 @@ let
# Import support libraries needed for protobuf & gRPC support # Import support libraries needed for protobuf & gRPC support
protoLibs = import ./proto.nix { protoLibs = import ./proto.nix {
inherit gopkgs; inherit external;
}; };
# Build a Go library out of the specified protobuf definition. # Build a Go library out of the specified protobuf definition.
proto = { name, proto ? null, protos ? [ proto ], path ? name, goPackage ? name, withGrpc ? false, extraSrcs ? [], extraDeps ? [] }: proto = { name, proto, path ? name, goPackage ? name, extraDeps ? [ ] }: (makeOverridable package) {
let
protosDir = runCommand "protos" {} ''
mkdir $out
${lib.concatMapStrings (p: "cp ${p} $out/${baseNameOf p}\n") protos}
'';
mname = prefix: lib.concatMapStrings (p: "${prefix}M${baseNameOf p}=${path} ") protos;
in (makeOverridable package) {
inherit name path; inherit name path;
deps = [ protoLibs.goProto.proto.gopkg ] ++ extraDeps; deps = [ protoLibs.goProto.proto.gopkg ] ++ extraDeps;
srcs = lib.concatMap (proto: lib.singleton (runCommand "goproto-${name}-${baseNameOf proto}.pb.go" {} '' srcs = lib.singleton (runCommand "goproto-${name}.pb.go" { } ''
${protobuf}/bin/protoc \ cp ${proto} ${baseNameOf proto}
-I ${protosDir} \ ${protobuf}/bin/protoc --plugin=${protoLibs.goProto.protoc-gen-go.gopkg}/bin/protoc-gen-go \
--plugin=${protoLibs.goProto.cmd.protoc-gen-go.gopkg}/bin/protoc-gen-go \ --go_out=plugins=grpc,import_path=${baseNameOf path}:. ${baseNameOf proto}
--go_out=. \ mv ./${goPackage}/*.pb.go $out
--go_opt=paths=source_relative \ '');
${mname "--go_opt="} \
${protosDir}/${baseNameOf proto}
mv ./*.pb.go $out
'') ++ lib.optional withGrpc (runCommand "gogrpcproto-${name}-${baseNameOf proto}.pb.go" {} ''
${protobuf}/bin/protoc \
-I ${protosDir} \
--plugin=${protoLibs.goGrpc.cmd.protoc-gen-go-grpc.gopkg}/bin/protoc-gen-go-grpc \
--go-grpc_out=. \
--go-grpc_opt=paths=source_relative \
${mname "--go-grpc_opt="} \
${protosDir}/${baseNameOf proto}
mv ./*.pb.go $out 2>/dev/null || echo "package ${goPackage}" >> $out
'')) protos ++ extraSrcs;
}; };
# Build a Go library out of the specified gRPC definition. # Build a Go library out of the specified gRPC definition.
grpc = { extraDeps ? [], ... }@args: proto (args // { withGrpc = true; extraDeps = extraDeps ++ [ protoLibs.goGrpc.gopkg ]; }); grpc = args: proto (args // { extraDeps = [ protoLibs.goGrpc.gopkg ]; });
in { in
{
# Only the high-level builder functions are exposed, but made # Only the high-level builder functions are exposed, but made
# overrideable. # overrideable.
program = makeOverridable program; program = makeOverridable program;
package = makeOverridable package; package = makeOverridable package;
proto = proto; proto = makeOverridable proto;
grpc = makeOverridable grpc; grpc = makeOverridable grpc;
external = makeOverridable external; external = makeOverridable external;
} }

View file

@ -8,7 +8,7 @@
# users a quick introduction to how to use buildGo. # users a quick introduction to how to use buildGo.
let let
buildGo = import ../default.nix {}; buildGo = import ../default.nix { };
# Example use of buildGo.package, which creates an importable Go # Example use of buildGo.package, which creates an importable Go
# package from the specified source files. # package from the specified source files.
@ -29,7 +29,8 @@ let
# Example use of buildGo.program, which builds an executable using # Example use of buildGo.program, which builds an executable using
# the specified name and dependencies (which in turn must have been # the specified name and dependencies (which in turn must have been
# created via buildGo.package etc.) # created via buildGo.package etc.)
in buildGo.program { in
buildGo.program {
name = "example"; name = "example";
srcs = [ srcs = [

View file

@ -17,12 +17,12 @@ let
inherit (pkgs) lib runCommand go jq ripgrep; inherit (pkgs) lib runCommand go jq ripgrep;
pathToName = p: replaceStrings ["/"] ["_"] (toString p); pathToName = p: replaceStrings [ "/" ] [ "_" ] (toString p);
# Collect all non-vendored dependencies from the Go standard library # Collect all non-vendored dependencies from the Go standard library
# into a file that can be used to filter them out when processing # into a file that can be used to filter them out when processing
# dependencies. # dependencies.
stdlibPackages = runCommand "stdlib-pkgs.json" {} '' stdlibPackages = runCommand "stdlib-pkgs.json" { } ''
export HOME=$PWD export HOME=$PWD
export GOPATH=/dev/null export GOPATH=/dev/null
${go}/bin/go list std | \ ${go}/bin/go list std | \
@ -45,20 +45,28 @@ let
}; };
mkset = path: value: mkset = path: value:
if path == [] then { gopkg = value; } if path == [ ] then { gopkg = value; }
else { "${head path}" = mkset (tail path) value; }; else { "${head path}" = mkset (tail path) value; };
last = l: elemAt l ((length l) - 1); last = l: elemAt l ((length l) - 1);
toPackage = self: src: path: depMap: entry: toPackage = self: src: path: depMap: entry:
let let
localDeps = map (d: lib.attrByPath (d ++ [ "gopkg" ]) ( localDeps = map
throw "missing local dependency '${lib.concatStringsSep "." d}' in '${path}'" (d: lib.attrByPath (d ++ [ "gopkg" ])
) self) entry.localDeps; (
throw "missing local dependency '${lib.concatStringsSep "." d}' in '${path}'"
)
self)
entry.localDeps;
foreignDeps = map (d: lib.attrByPath [ d ] ( foreignDeps = map
throw "missing foreign dependency '${d}' in '${path}'" (d: lib.attrByPath [ d.path ]
) depMap) entry.foreignDeps; (
throw "missing foreign dependency '${d.path}' in '${path}, imported at ${d.position}'"
)
depMap)
entry.foreignDeps;
args = { args = {
srcs = map (f: src + ("/" + f)) entry.files; srcs = map (f: src + ("/" + f)) entry.files;
@ -74,22 +82,28 @@ let
binArgs = args // { binArgs = args // {
name = (last ((lib.splitString "/" path) ++ entry.locator)); name = (last ((lib.splitString "/" path) ++ entry.locator));
}; };
in if entry.isCommand then (program binArgs) else (package libArgs); in
if entry.isCommand then (program binArgs) else (package libArgs);
in { src, path, deps ? [] }: let in
{ src, path, deps ? [ ] }:
let
# Build a map of dependencies (from their import paths to their # Build a map of dependencies (from their import paths to their
# derivation) so that they can be conditionally imported only in # derivation) so that they can be conditionally imported only in
# sub-packages that require them. # sub-packages that require them.
depMap = listToAttrs (map (d: { depMap = listToAttrs (map
name = d.goImportPath; (d: {
value = d; name = d.goImportPath;
}) (map (d: d.gopkg) deps)); value = d;
})
(map (d: d.gopkg) deps));
name = pathToName path; name = pathToName path;
analysisOutput = runCommand "${name}-structure.json" {} '' analysisOutput = runCommand "${name}-structure.json" { } ''
${analyser}/bin/analyser -path ${path} -source ${src} > $out ${analyser}/bin/analyser -path ${path} -source ${src} > $out
''; '';
analysis = fromJSON (readFile analysisOutput); analysis = fromJSON (readFile analysisOutput);
in lib.fix(self: foldl' lib.recursiveUpdate {} ( in
lib.fix (self: foldl' lib.recursiveUpdate { } (
map (entry: mkset entry.locator (toPackage self src path depMap entry)) analysis map (entry: mkset entry.locator (toPackage self src path depMap entry)) analysis
)) ))

View file

@ -29,13 +29,19 @@ var stdlibList string
// Return information includes the local (relative from project root) // Return information includes the local (relative from project root)
// and external (none-stdlib) dependencies of this package. // and external (none-stdlib) dependencies of this package.
type pkg struct { type pkg struct {
Name string `json:"name"` Name string `json:"name"`
Locator []string `json:"locator"` Locator []string `json:"locator"`
Files []string `json:"files"` Files []string `json:"files"`
SFiles []string `json:"sfiles"` SFiles []string `json:"sfiles"`
LocalDeps [][]string `json:"localDeps"` LocalDeps [][]string `json:"localDeps"`
ForeignDeps []string `json:"foreignDeps"` ForeignDeps []foreignDep `json:"foreignDeps"`
IsCommand bool `json:"isCommand"` IsCommand bool `json:"isCommand"`
}
type foreignDep struct {
Path string `json:"path"`
// filename, column and line number of the import, if known
Position string `json:"position"`
} }
// findGoDirs returns a filepath.WalkFunc that identifies all // findGoDirs returns a filepath.WalkFunc that identifies all
@ -88,7 +94,7 @@ func analysePackage(root, source, importpath string, stdlib map[string]bool) (pk
} }
local := [][]string{} local := [][]string{}
foreign := []string{} foreign := []foreignDep{}
for _, i := range p.Imports { for _, i := range p.Imports {
if stdlib[i] { if stdlib[i] {
@ -100,7 +106,12 @@ func analysePackage(root, source, importpath string, stdlib map[string]bool) (pk
} else if strings.HasPrefix(i, importpath+"/") { } else if strings.HasPrefix(i, importpath+"/") {
local = append(local, strings.Split(strings.TrimPrefix(i, importpath+"/"), "/")) local = append(local, strings.Split(strings.TrimPrefix(i, importpath+"/"), "/"))
} else { } else {
foreign = append(foreign, i) // The import positions is a map keyed on the import name.
// The value is a list, presumably because an import can appear
// multiple times in a package. Lets just take the first one,
// should be enough for a good error message.
firstPos := p.ImportPos[i][0].String()
foreign = append(foreign, foreignDep{Path: i, Position: firstPos})
} }
} }

View file

@ -4,11 +4,84 @@
# This file provides derivations for the dependencies of a gRPC # This file provides derivations for the dependencies of a gRPC
# service in Go. # service in Go.
{ gopkgs }: { external }:
let let
inherit (builtins) fetchGit map; inherit (builtins) fetchGit map;
in rec { in
goProto = gopkgs."google.golang.org".protobuf; rec {
goGrpc = gopkgs."google.golang.org".grpc; goProto = external {
path = "github.com/golang/protobuf";
src = fetchGit {
url = "https://github.com/golang/protobuf";
rev = "ed6926b37a637426117ccab59282c3839528a700";
};
};
xnet = external {
path = "golang.org/x/net";
src = fetchGit {
url = "https://go.googlesource.com/net";
rev = "ffdde105785063a81acd95bdf89ea53f6e0aac2d";
};
deps = [
xtext.secure.bidirule
xtext.unicode.bidi
xtext.unicode.norm
];
};
xsys = external {
path = "golang.org/x/sys";
src = fetchGit {
url = "https://go.googlesource.com/sys";
rev = "bd437916bb0eb726b873ee8e9b2dcf212d32e2fd";
};
};
xtext = external {
path = "golang.org/x/text";
src = fetchGit {
url = "https://go.googlesource.com/text";
rev = "cbf43d21aaebfdfeb81d91a5f444d13a3046e686";
};
};
genproto = external {
path = "google.golang.org/genproto";
src = fetchGit {
url = "https://github.com/google/go-genproto";
# necessary because https://github.com/NixOS/nix/issues/1923
ref = "main";
rev = "83cc0476cb11ea0da33dacd4c6354ab192de6fe6";
};
deps = with goProto; [
proto
ptypes.any
];
};
goGrpc = external {
path = "google.golang.org/grpc";
deps = ([
xnet.trace
xnet.http2
xsys.unix
xnet.http2.hpack
genproto.googleapis.rpc.status
] ++ (with goProto; [
proto
ptypes
ptypes.duration
ptypes.timestamp
]));
src = fetchGit {
url = "https://github.com/grpc/grpc-go";
rev = "d8e3da36ac481ef00e510ca119f6b68177713689";
};
};
} }

View file

@ -6,18 +6,24 @@ This is a build system for Common Lisp, written in Nix.
It aims to offer an alternative to ASDF for users who live in a It aims to offer an alternative to ASDF for users who live in a
Nix-based ecosystem. This offers several advantages over ASDF: Nix-based ecosystem. This offers several advantages over ASDF:
* Simpler (logic-less) package definitions * Simpler (almost logic-less) package definitions
* Easy linking of native dependencies (from Nix) * Easy linking of native dependencies (from Nix)
* Composability with Nix tooling for other languages * Composability with Nix tooling for other languages
* Effective, per-system caching strategies * Effective, per-system caching strategies
* Easy overriding of dependencies and whatnot * Easy overriding of dependencies and whatnot
* Convenient support for multiple Common Lisp implementations
* ... and more! * ... and more!
The project is still in its early stages and some important The project is still in its early stages and some important
restrictions should be highlighted: restrictions should be highlighted:
* Only SBCL is supported (though the plan is to add support for at * Extending `buildLisp` with support for a custom implementation
least ABCL and Clozure CL, and maybe make it extensible) currently requires some knowledge of internals and may not be
considered stable yet.
* Parallel compilation is not possible: Since buildLisp doesn't encode
dependencies between components (i. e. source files) like ASDF,
it must compile source files in sequence to avoid errors due to
undefined symbols.
## Usage ## Usage
@ -32,6 +38,7 @@ restrictions should be highlighted:
| `deps` | `list<drv>` | List of dependencies | no | | `deps` | `list<drv>` | List of dependencies | no |
| `native` | `list<drv>` | List of native dependencies | no | | `native` | `list<drv>` | List of native dependencies | no |
| `test` | see "Tests" | Specification for test suite | no | | `test` | see "Tests" | Specification for test suite | no |
| `implementation` | see "Implementations" | Common Lisp implementation to use | no |
The output of invoking this is a directory containing a FASL file The output of invoking this is a directory containing a FASL file
that is the concatenated result of all compiled sources. that is the concatenated result of all compiled sources.
@ -46,6 +53,7 @@ restrictions should be highlighted:
| `native` | `list<drv>` | List of native dependencies | no | | `native` | `list<drv>` | List of native dependencies | no |
| `main` | `string` | Entrypoint function | no | | `main` | `string` | Entrypoint function | no |
| `test` | see "Tests" | Specification for test suite | no | | `test` | see "Tests" | Specification for test suite | no |
| `implementation` | see "Implementations" | Common Lisp implementation to use | no |
The `main` parameter should be the name of a function and defaults The `main` parameter should be the name of a function and defaults
to `${name}:main` (i.e. the *exported* `main` function of the to `${name}:main` (i.e. the *exported* `main` function of the
@ -64,13 +72,6 @@ restrictions should be highlighted:
built-in library and returns a "package" that simply requires this built-in library and returns a "package" that simply requires this
library. library.
* `buildLisp.sbclWith`: Creates an SBCL pre-loaded with various dependencies.
This function takes a single argument which is a list of Lisp
libraries programs or programs. It creates an SBCL that is
pre-loaded with all of that Lisp code and can be used as the host
for e.g. Sly or SLIME.
## Tests ## Tests
Both `buildLisp.library` and `buildLisp.program` take an optional argument Both `buildLisp.library` and `buildLisp.program` take an optional argument
@ -115,3 +116,139 @@ in buildLisp.program {
}; };
} }
``` ```
## Development REPLs
`buildLisp` builds loadable variants of both `program` and `library` derivations
(usually FASL files). Therefore it can provide a convenient way to obtain an
instance of any implementation preloaded with `buildLisp`-derivations. This
is especially useful to use as a host for Sly or SLIME.
* `buildLisp.sbcl.lispWith`, `buildLisp.ccl.lispWith`, ...:
Creates a wrapper script preloading a Lisp implementation with various dependencies.
This function takes a single argument which is a list of Lisp
libraries programs or programs. The desired Lisp implementation
will load all given derivations and all their dependencies on
startup.
The shortcut `buildLisp.sbclWith` for `buildLisp.sbcl.lispWith` is also provided.
* `repl` passthru attribute: `derivation.repl` is provided as a shortcut
for `buildLisp.${implementationName}.lispWith [ derivation ]`.
`derivation.ccl.repl`, `derivation.sbcl.repl` etc. work as well, of course
(see also "Implementations" section).
## Implementations
Both `buildLisp.library` and `buildLisp.program` allow specifying a different
Common Lisp implementation than the default one (which is SBCL). When an
implementation is passed, `buildLisp` makes sure all dependencies are built
with that implementation as well since build artifacts from different
implementation will be incompatible with each other.
The argument taken by `implementation` is a special attribute set which
describes how to do certain tasks for a given implementation, like building
or loading a library. In case you want to use a custom implementation
description, the precise structure needed is documented in `buildLisp`'s
source code for now. `buildLisp` also exposes the following already
working implementation sets:
* `buildLisp.sbcl`: [SBCL][sbcl], our default implementation
* `buildLisp.ccl`: [CCL][ccl], similar to SBCL, but with very good macOS support
* `buildLisp.ecl`: [ECL][ecl] setup to produce statically linked binaries and
libraries. Note that its runtime library is LGPL, so [extra conditions][lgpl-static]
must be fulfilled when distributing binaries produced this way.
* Support for ABCL is planned.
For every of these “known” implementations, `buildLisp` will create a `passthru`
attribute named like the implementation which points to a variant of the derivation
built with said implementation. Say we have a derivation, `myDrv`, built using SBCL:
While `myDrv` and `myDrv.sbcl` are built using SBCL, `myDrv.ecl`, `myDrv.ccl` etc.
build the derivation and all its dependencies using ECL and CCL respectively.
This is useful to test portability of your derivation, but is also used internally
to speed up the “normalization” of the dependency graph. Thus it is important to
make sure that your custom implementation's name doesn't clash with one of the
“known” ones.
## Handling Implementation Specifics
When targeting multiple Common Lisp implementation, it is often necessary to
handle differing interfaces for OS interaction or to make use of special
implementation features. For this reason, `buildLisp` allows specifying
dependencies and source files for specific implementations only. This can
be utilized by having an attribute set in the list for the `deps` or `srcs`
argument: `buildLisp` will pick the value of the attribute named like the
used implementation or `default` and ignore the set completely if both
are missing.
```nix
{ buildLisp, lispPkgs }:
buildLisp.library {
name = "mylib";
srcs = [
# These are included always of course
./package.lisp
./portable-lib.lisp
# Choose right impl-* file
{
sbcl = ./impl-sbcl.lisp;
ccl = ./impl-ccl.lisp;
ecl = ./impl-ecl.lisp;
}
# We can also use this to inject extra files
{ ecl = ./extra-ecl-optimizations.lisp; }
];
deps = [
# Use SBCL's special bundled package, flexi-streams otherwise
{
sbcl = buildLisp.bundled "sb-rotate-byte";
default = lispPkgs.flexi-streams;
}
];
}
```
Additionally a `brokenOn` parameter is accepted which takes a list of
implementation names on which the derivation is not expected to work.
This only influences `meta.ci.targets` which is read by depot's CI to
check which variants (see "Implementations") of the derivation to
build, so it may not be useful outside of depot.
## Influencing the Lisp Runtime
Lisp implementations which create an executable by dumping an image
usually parse a few implementation-specific command line options on
executable startup that influence runtime settings related to things
like GC. `buildLisp` generates a wrapper which makes sure that this
never interferes with the argument parsing implemented in the actual
application, but sometimes it is useful to run an executable with
special settings. To allow this, the content of `NIX_BUILDLISP_LISP_ARGS`
is passed to the lisp implementation.
For example, you can make the underlying SBCL print its version for
any executable built with `buildLisp` (and SBCL) like this:
```console
$ env NIX_BUILDLISP_LISP_ARGS="--version" ./result/bin/🕰️
SBCL 2.1.2.nixos
```
In practice you'd probably want to specify options like
`--dynamic-space-size` or `--tls-limit` (try passing `--help` for a
full list). Naturally, these options are completely different for
different implementations.
[sbcl]: http://www.sbcl.org/
[ccl]: https://ccl.clozure.com/
[ecl]: https://common-lisp.net/project/ecl/
[lgpl-static]: https://www.gnu.org/licenses/gpl-faq.en.html#LGPLStaticVsDynamic

View file

@ -4,67 +4,95 @@
# buildLisp is designed to enforce conventions and do away with the # buildLisp is designed to enforce conventions and do away with the
# free-for-all of existing Lisp build systems. # free-for-all of existing Lisp build systems.
{ pkgs ? import <nixpkgs> {}, ... }: { pkgs ? import <nixpkgs> { }, ... }:
let let
inherit (builtins) map elemAt match filter; inherit (builtins) map elemAt match filter;
inherit (pkgs) lib runCommandNoCC makeWrapper writeText writeShellScriptBin sbcl; inherit (pkgs) lib runCommandNoCC makeWrapper writeText writeShellScriptBin sbcl ecl-static ccl;
inherit (pkgs.stdenv) targetPlatform;
# #
# Internal helper definitions # Internal helper definitions
# #
# 'genLoadLisp' generates Lisp code that instructs SBCL to load all defaultImplementation = impls.sbcl;
# the provided Lisp libraries.
genLoadLisp = deps: lib.concatStringsSep "\n"
(map (lib: "(load \"${lib}/${lib.lispName}.fasl\")") (allDeps deps));
# 'genCompileLisp' generates a Lisp file that instructs SBCL to # Many Common Lisp implementations (like ECL and CCL) will occasionally drop
# compile the provided list of Lisp source files to $out. # you into an interactive debugger even when executing something as a script.
genCompileLisp = srcs: deps: writeText "compile.lisp" '' # In nix builds we don't want such a situation: Any error should make the
;; This file compiles the specified sources into the Nix build # script exit non-zero. Luckily the ANSI standard specifies *debugger-hook*
;; directory, creating one FASL file for each source. # which is invoked before the debugger letting us just do that.
(require 'sb-posix) disableDebugger = writeText "disable-debugger.lisp" ''
(setf *debugger-hook*
${genLoadLisp deps} (lambda (error hook)
(declare (ignore hook))
(defun nix-compile-lisp (file srcfile) (format *error-output* "~%Unhandled error: ~a~%" error)
(let ((outfile (make-pathname :type "fasl" #+ccl (quit 1)
:directory (or (sb-posix:getenv "NIX_BUILD_TOP") #+ecl (ext:quit 1)))
(error "not running in a Nix build"))
:name (substitute #\- #\/ srcfile))))
(multiple-value-bind (_outfile _warnings-p failure-p)
(compile-file srcfile :output-file outfile)
(if failure-p (sb-posix:exit 1)
(progn
;; For the case of multiple files belonging to the same
;; library being compiled, load them in order:
(load outfile)
;; Write them to the FASL list in the same order:
(format file "cat ~a~%" (namestring outfile)))))))
(let ((*compile-verbose* t)
;; FASL files are compiled into the working directory of the
;; build and *then* moved to the correct out location.
(pwd (sb-posix:getcwd)))
(with-open-file (file "cat_fasls"
:direction :output
:if-does-not-exist :create)
;; These forms were inserted by the Nix build:
${
lib.concatStringsSep "\n" (map (src: "(nix-compile-lisp file \"${src}\")") srcs)
}
))
''; '';
# 'genTestLisp' generates a Lisp file that loads all sources and deps and # Process a list of arbitrary values which also contains “implementation
# executes expression # filter sets” which describe conditonal inclusion of elements depending
genTestLisp = name: srcs: deps: expression: writeText "${name}.lisp" '' # on the CL implementation used. Elements are processed in the following
# manner:
#
# * Paths, strings, derivations are left as is
# * A non-derivation attribute set is processed like this:
# 1. If it has an attribute equal to impl.name, replace with its value.
# 2. Alternatively use the value of the "default" attribute.
# 3. In all other cases delete the element from the list.
#
# This can be used to express dependencies or source files which are specific
# to certain implementations:
#
# srcs = [
# # mixable with unconditional entries
# ./package.lisp
#
# # implementation specific source files
# {
# ccl = ./impl-ccl.lisp;
# sbcl = ./impl-sbcl.lisp;
# ecl = ./impl-ecl.lisp;
# }
# ];
#
# deps = [
# # this dependency is ignored if impl.name != "sbcl"
# { sbcl = buildLisp.bundled "sb-posix"; }
#
# # only special casing for a single implementation
# {
# sbcl = buildLisp.bundled "uiop";
# default = buildLisp.bundled "asdf";
# }
# ];
implFilter = impl: xs:
let
isFilterSet = x: builtins.isAttrs x && !(lib.isDerivation x);
in
builtins.map
(
x: if isFilterSet x then x.${impl.name} or x.default else x
)
(builtins.filter
(
x: !(isFilterSet x) || x ? ${impl.name} || x ? default
)
xs);
# Generates lisp code which instructs the given lisp implementation to load
# all the given dependencies.
genLoadLispGeneric = impl: deps:
lib.concatStringsSep "\n"
(map (lib: "(load \"${lib}/${lib.lispName}.${impl.faslExt}\")")
(allDeps impl deps));
# 'genTestLispGeneric' generates a Lisp file that loads all sources and deps
# and executes expression for a given implementation description.
genTestLispGeneric = impl: { name, srcs, deps, expression }: writeText "${name}.lisp" ''
;; Dependencies ;; Dependencies
${genLoadLisp deps} ${impl.genLoadLisp deps}
;; Sources ;; Sources
${lib.concatStringsSep "\n" (map (src: "(load \"${src}\")") srcs)} ${lib.concatStringsSep "\n" (map (src: "(load \"${src}\")") srcs)}
@ -78,10 +106,23 @@ let
dependsOn = a: b: builtins.elem a b.lispDeps; dependsOn = a: b: builtins.elem a b.lispDeps;
# 'allDeps' flattens the list of dependencies (and their # 'allDeps' flattens the list of dependencies (and their
# dependencies) into one ordered list of unique deps. # dependencies) into one ordered list of unique deps which
allDeps = deps: (lib.toposort dependsOn (lib.unique ( # all use the given implementation.
lib.flatten (deps ++ (map (d: d.lispDeps) deps)) allDeps = impl: deps:
))).result; let
# The override _should_ propagate itself recursively, as every derivation
# would only expose its actually used dependencies. Use implementation
# attribute created by withExtras if present, override in all other cases
# (mainly bundled).
deps' = builtins.map
(dep: dep."${impl.name}" or (dep.overrideLisp (_: {
implementation = impl;
})))
deps;
in
(lib.toposort dependsOn (lib.unique (
lib.flatten (deps' ++ (map (d: d.lispDeps) deps'))
))).result;
# 'allNative' extracts all native dependencies of a dependency list # 'allNative' extracts all native dependencies of a dependency list
# to ensure that library load paths are set correctly during all # to ensure that library load paths are set correctly during all
@ -90,26 +131,6 @@ let
lib.flatten (native ++ (map (d: d.lispNativeDeps) deps)) lib.flatten (native ++ (map (d: d.lispNativeDeps) deps))
); );
# 'genDumpLisp' generates a Lisp file that instructs SBCL to dump
# the currently loaded image as an executable to $out/bin/$name.
#
# TODO(tazjin): Compression is currently unsupported because the
# SBCL in nixpkgs is, by default, not compiled with zlib support.
genDumpLisp = name: main: deps: writeText "dump.lisp" ''
(require 'sb-posix)
${genLoadLisp deps}
(let* ((bindir (concatenate 'string (sb-posix:getenv "out") "/bin"))
(outpath (make-pathname :name "${name}"
:directory bindir)))
(save-lisp-and-die outpath
:executable t
:toplevel (function ${main})
:purify t))
;;
'';
# Add an `overrideLisp` attribute to a function result that works # Add an `overrideLisp` attribute to a function result that works
# similar to `overrideAttrs`, but is used specifically for the # similar to `overrideAttrs`, but is used specifically for the
# arguments passed to Lisp builders. # arguments passed to Lisp builders.
@ -117,142 +138,642 @@ let
overrideLisp = new: makeOverridable f (orig // (new orig)); overrideLisp = new: makeOverridable f (orig // (new orig));
}; };
# This is a wrapper arround 'makeOverridable' which performs its
# function, but also adds a the following additional attributes to the
# resulting derivation, namely a repl attribute which builds a `lispWith`
# derivation for the current implementation and additional attributes for
# every all implementations. So `drv.sbcl` would build the derivation
# with SBCL regardless of what was specified in the initial arguments.
withExtras = f: args:
let
drv = (makeOverridable f) args;
in
lib.fix (self:
drv.overrideLisp
(old:
let
implementation = old.implementation or defaultImplementation;
brokenOn = old.brokenOn or [ ];
targets = lib.subtractLists (brokenOn ++ [ implementation.name ])
(builtins.attrNames impls);
in
{
passthru = (old.passthru or { }) // {
repl = implementation.lispWith [ self ];
# meta is done via passthru to minimize rebuilds caused by overriding
meta = (old.passthru.meta or { }) // {
ci = (old.passthru.meta.ci or { }) // {
inherit targets;
};
};
} // builtins.listToAttrs (builtins.map
(impl: {
inherit (impl) name;
value = self.overrideLisp (_: {
implementation = impl;
});
})
(builtins.attrValues impls));
}) // {
overrideLisp = new: withExtras f (args // new args);
});
# 'testSuite' builds a Common Lisp test suite that loads all of srcs and deps, # 'testSuite' builds a Common Lisp test suite that loads all of srcs and deps,
# and then executes expression to check its result # and then executes expression to check its result
testSuite = { name, expression, srcs, deps ? [], native ? [] }: testSuite = { name, expression, srcs, deps ? [ ], native ? [ ], implementation }:
let let
lispNativeDeps = allNative native deps; lispDeps = allDeps implementation (implFilter implementation deps);
lispDeps = allDeps deps; lispNativeDeps = allNative native lispDeps;
in runCommandNoCC name { filteredSrcs = implFilter implementation srcs;
LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps; in
LANG = "C.UTF-8"; runCommandNoCC name
} '' {
LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps;
LANG = "C.UTF-8";
} ''
echo "Running test suite ${name}" echo "Running test suite ${name}"
${sbcl}/bin/sbcl --script ${genTestLisp name srcs deps expression} \ ${implementation.runScript} ${
| tee $out implementation.genTestLisp {
inherit name expression;
srcs = filteredSrcs;
deps = lispDeps;
}
} | tee $out
echo "Test suite ${name} succeeded" echo "Test suite ${name} succeeded"
''; '';
# 'impls' is an attribute set of attribute sets which describe how to do common
# tasks when building for different Common Lisp implementations. Each
# implementation set has the following members:
#
# Required members:
#
# - runScript :: string
# Describes how to invoke the implementation from the shell, so it runs a
# lisp file as a script and exits.
# - faslExt :: string
# File extension of the implementations loadable (FASL) files.
# Implementations are free to generate native object files, but with the way
# buildLisp works it is required that we can also 'load' libraries, so
# (additionally) building a FASL or equivalent is required.
# - genLoadLisp :: [ dependency ] -> string
# Returns lisp code to 'load' the given dependencies. 'genLoadLispGeneric'
# should work for most dependencies.
# - genCompileLisp :: { name, srcs, deps } -> file
# Builds a lisp file which instructs the implementation to build a library
# from the given source files when executed. After running at least
# the file "$out/${name}.${impls.${implementation}.faslExt}" should have
# been created.
# - genDumpLisp :: { name, main, deps } -> file
# Builds a lisp file which instructs the implementation to build an
# executable which runs 'main' (and exits) where 'main' is available from
# 'deps'. The executable should be created as "$out/bin/${name}", usually
# by dumping the lisp image with the replaced toplevel function replaced.
# - wrapProgram :: boolean
# Whether to wrap the resulting binary / image with a wrapper script setting
# `LD_LIBRARY_PATH`.
# - genTestLisp :: { name, srcs, deps, expression } -> file
# Builds a lisp file which loads the given 'deps' and 'srcs' files and
# then evaluates 'expression'. Depending on whether 'expression' returns
# true or false, the script must exit with a zero or non-zero exit code.
# 'genTestLispGeneric' will work for most implementations.
# - lispWith :: [ dependency ] -> drv
# Builds a script (or dumped image) which when executed loads (or has
# loaded) all given dependencies. When built this should create an executable
# at "$out/bin/${implementation}".
#
# Optional members:
#
# - bundled :: string -> library
# Allows giving an implementation specific builder for a bundled library.
# This function is used as a replacement for the internal defaultBundled
# function and only needs to support one implementation. The returned derivation
# must behave like one built by 'library' (in particular have the same files
# available in "$out" and the same 'passthru' attributes), but may be built
# completely differently.
impls = lib.mapAttrs (name: v: { inherit name; } // v) {
sbcl = {
runScript = "${sbcl}/bin/sbcl --script";
faslExt = "fasl";
# 'genLoadLisp' generates Lisp code that instructs SBCL to load all
# the provided Lisp libraries.
genLoadLisp = genLoadLispGeneric impls.sbcl;
# 'genCompileLisp' generates a Lisp file that instructs SBCL to
# compile the provided list of Lisp source files to "$out/${name}.fasl".
genCompileLisp = { name, srcs, deps }: writeText "sbcl-compile.lisp" ''
;; This file compiles the specified sources into the Nix build
;; directory, creating one FASL file for each source.
(require 'sb-posix)
${impls.sbcl.genLoadLisp deps}
(defun nix-compile-lisp (srcfile)
(let ((outfile (make-pathname :type "fasl"
:directory (or (sb-posix:getenv "NIX_BUILD_TOP")
(error "not running in a Nix build"))
:name (substitute #\- #\/ srcfile))))
(multiple-value-bind (out-truename _warnings-p failure-p)
(compile-file srcfile :output-file outfile)
(if failure-p (sb-posix:exit 1)
(progn
;; For the case of multiple files belonging to the same
;; library being compiled, load them in order:
(load out-truename)
;; Return pathname as a string for cat-ting it later
(namestring out-truename))))))
(let ((*compile-verbose* t)
(catted-fasl (make-pathname :type "fasl"
:directory (or (sb-posix:getenv "out")
(error "not running in a Nix build"))
:name "${name}")))
(with-open-file (file catted-fasl
:direction :output
:if-does-not-exist :create)
;; SBCL's FASL files can just be bundled together using cat
(sb-ext:run-program "cat"
(mapcar #'nix-compile-lisp
;; These forms were inserted by the Nix build:
'(${
lib.concatMapStringsSep "\n" (src: "\"${src}\"") srcs
}))
:output file :search t)))
'';
# 'genDumpLisp' generates a Lisp file that instructs SBCL to dump
# the currently loaded image as an executable to $out/bin/$name.
#
# TODO(tazjin): Compression is currently unsupported because the
# SBCL in nixpkgs is, by default, not compiled with zlib support.
genDumpLisp = { name, main, deps }: writeText "sbcl-dump.lisp" ''
(require 'sb-posix)
${impls.sbcl.genLoadLisp deps}
(let* ((bindir (concatenate 'string (sb-posix:getenv "out") "/bin"))
(outpath (make-pathname :name "${name}"
:directory bindir)))
;; Tell UIOP that argv[0] will refer to running image, not the lisp impl
(when (find-package :uiop)
(eval `(setq ,(find-symbol "*IMAGE-DUMPED-P*" :uiop) :executable)))
(save-lisp-and-die outpath
:executable t
:toplevel
(lambda ()
;; Filter out everything prior to the `--` we
;; insert in the wrapper to prevent SBCL from
;; parsing arguments at startup
(setf sb-ext:*posix-argv*
(delete "--" sb-ext:*posix-argv*
:test #'string= :count 1))
(${main}))
:purify t))
'';
wrapProgram = true;
genTestLisp = genTestLispGeneric impls.sbcl;
lispWith = deps:
let lispDeps = filter (d: !d.lispBinary) (allDeps impls.sbcl deps);
in writeShellScriptBin "sbcl" ''
export LD_LIBRARY_PATH="${lib.makeLibraryPath (allNative [] lispDeps)}"
export LANG="C.UTF-8"
exec ${sbcl}/bin/sbcl ${
lib.optionalString (deps != [])
"--load ${writeText "load.lisp" (impls.sbcl.genLoadLisp lispDeps)}"
} $@
'';
};
ecl = {
runScript = "${ecl-static}/bin/ecl --load ${disableDebugger} --shell";
faslExt = "fasc";
genLoadLisp = genLoadLispGeneric impls.ecl;
genCompileLisp = { name, srcs, deps }: writeText "ecl-compile.lisp" ''
;; This seems to be required to bring make the 'c' package available
;; early, otherwise ECL tends to fail with a read failure
(ext:install-c-compiler)
;; Load dependencies
${impls.ecl.genLoadLisp deps}
(defun getenv-or-fail (var)
(or (ext:getenv var)
(error (format nil "Missing expected environment variable ~A" var))))
(defun nix-compile-file (srcfile &key native)
"Compile the given srcfile into a compilation unit in :out-dir using
a unique name based on srcfile as the filename which is returned after
compilation. If :native is true, create an native object file,
otherwise a byte-compile fasc file is built and immediately loaded."
(let* ((unique-name (substitute #\_ #\/ srcfile))
(out-file (make-pathname :type (if native "o" "fasc")
:directory (getenv-or-fail "NIX_BUILD_TOP")
:name unique-name)))
(multiple-value-bind (out-truename _warnings-p failure-p)
(compile-file srcfile :system-p native
:load (not native)
:output-file out-file
:verbose t :print t)
(if failure-p (ext:quit 1) out-truename))))
(let* ((out-dir (getenv-or-fail "out"))
(nix-build-dir (getenv-or-fail "NIX_BUILD_TOP"))
(srcs
;; These forms are inserted by the Nix build
'(${lib.concatMapStringsSep "\n" (src: "\"${src}\"") srcs})))
;; First, we'll byte compile loadable FASL files and load them
;; immediately. Since we are using a statically linked ECL, there's
;; no way to load native objects, so we rely on byte compilation
;; for all our loading which is crucial in compilation of course.
(ext:install-bytecodes-compiler)
;; ECL's bytecode FASLs can just be concatenated to create a bundle
;; at least since a recent bugfix which we apply as a patch.
;; See also: https://gitlab.com/embeddable-common-lisp/ecl/-/issues/649
(let ((bundle-out (make-pathname :type "fasc" :name "${name}"
:directory out-dir)))
(with-open-file (fasc-stream bundle-out :direction :output)
(ext:run-program "cat"
(mapcar (lambda (f)
(namestring
(nix-compile-file f :native nil)))
srcs)
:output fasc-stream)))
(ext:install-c-compiler)
;; Build a (natively compiled) static archive (.a) file. We want to
;; use this for (statically) linking an executable later. The bytecode
;; dance is only required because we can't load such archives.
(c:build-static-library
(make-pathname :type "a" :name "${name}" :directory out-dir)
:lisp-files (mapcar (lambda (x)
(nix-compile-file x :native t))
srcs)))
'';
genDumpLisp = { name, main, deps }: writeText "ecl-dump.lisp" ''
(defun getenv-or-fail (var)
(or (ext:getenv var)
(error (format nil "Missing expected environment variable ~A" var))))
${impls.ecl.genLoadLisp deps}
;; makes a 'c' package available that can link executables
(ext:install-c-compiler)
(c:build-program
(merge-pathnames (make-pathname :directory '(:relative "bin")
:name "${name}")
(truename (getenv-or-fail "out")))
:epilogue-code `(progn
;; UIOP doesn't understand ECL, so we need to make it
;; aware that we are a proper executable, causing it
;; to handle argument parsing and such properly. Since
;; this needs to work even when we're not using UIOP,
;; we need to do some compile-time acrobatics.
,(when (find-package :uiop)
`(setf ,(find-symbol "*IMAGE-DUMPED-P*" :uiop) :executable))
;; Run the actual application
(${main})
;; and exit.
(ext:quit))
;; ECL can't remember these from its own build
:ld-flags '("-static")
:lisp-files
;; The following forms are inserted by the Nix build
'(${
lib.concatMapStrings (dep: ''
"${dep}/${dep.lispName}.a"
'') (allDeps impls.ecl deps)
}))
'';
wrapProgram = false;
genTestLisp = genTestLispGeneric impls.ecl;
lispWith = deps:
let lispDeps = filter (d: !d.lispBinary) (allDeps impls.ecl deps);
in writeShellScriptBin "ecl" ''
exec ${ecl-static}/bin/ecl ${
lib.optionalString (deps != [])
"--load ${writeText "load.lisp" (impls.ecl.genLoadLisp lispDeps)}"
} $@
'';
bundled = name: runCommandNoCC "${name}-cllib"
{
passthru = {
lispName = name;
lispNativeDeps = [ ];
lispDeps = [ ];
lispBinary = false;
repl = impls.ecl.lispWith [ (impls.ecl.bundled name) ];
};
} ''
mkdir -p "$out"
ln -s "${ecl-static}/lib/ecl-${ecl-static.version}/${name}.${impls.ecl.faslExt}" -t "$out"
ln -s "${ecl-static}/lib/ecl-${ecl-static.version}/lib${name}.a" "$out/${name}.a"
'';
};
ccl = {
# Relatively bespoke wrapper script necessary to make CCL just™ execute
# a lisp file as a script.
runScript = pkgs.writers.writeBash "ccl" ''
# don't print intro message etc.
args=("--quiet")
# makes CCL crash on error instead of entering the debugger
args+=("--load" "${disableDebugger}")
# load files from command line in order
for f in "$@"; do
args+=("--load" "$f")
done
# Exit if everything was processed successfully
args+=("--eval" "(quit)")
exec ${ccl}/bin/ccl ''${args[@]}
'';
# See https://ccl.clozure.com/docs/ccl.html#building-definitions
faslExt =
/**/
if targetPlatform.isPowerPC && targetPlatform.is32bit then "pfsl"
else if targetPlatform.isPowerPC && targetPlatform.is64bit then "p64fsl"
else if targetPlatform.isx86_64 && targetPlatform.isLinux then "lx64fsl"
else if targetPlatform.isx86_32 && targetPlatform.isLinux then "lx32fsl"
else if targetPlatform.isAarch32 && targetPlatform.isLinux then "lafsl"
else if targetPlatform.isx86_32 && targetPlatform.isDarwin then "dx32fsl"
else if targetPlatform.isx86_64 && targetPlatform.isDarwin then "dx64fsl"
else if targetPlatform.isx86_64 && targetPlatform.isDarwin then "dx64fsl"
else if targetPlatform.isx86_32 && targetPlatform.isFreeBSD then "fx32fsl"
else if targetPlatform.isx86_64 && targetPlatform.isFreeBSD then "fx64fsl"
else if targetPlatform.isx86_32 && targetPlatform.isWindows then "wx32fsl"
else if targetPlatform.isx86_64 && targetPlatform.isWindows then "wx64fsl"
else builtins.throw "Don't know what FASLs are called for this platform: "
+ pkgs.stdenv.targetPlatform.system;
genLoadLisp = genLoadLispGeneric impls.ccl;
genCompileLisp = { name, srcs, deps }: writeText "ccl-compile.lisp" ''
${impls.ccl.genLoadLisp deps}
(defun getenv-or-fail (var)
(or (getenv var)
(error (format nil "Missing expected environment variable ~A" var))))
(defun nix-compile-file (srcfile)
"Trivial wrapper around COMPILE-FILE which causes CCL to exit if
compilation fails and LOADs the compiled file on success."
(let ((output (make-pathname :name (substitute #\_ #\/ srcfile)
:type "${impls.ccl.faslExt}"
:directory (getenv-or-fail "NIX_BUILD_TOP"))))
(multiple-value-bind (out-truename _warnings-p failure-p)
(compile-file srcfile :output-file output :print t :verbose t)
(declare (ignore _warnings-p))
(if failure-p (quit 1)
(progn (load out-truename) out-truename)))))
(fasl-concatenate (make-pathname :name "${name}" :type "${impls.ccl.faslExt}"
:directory (getenv-or-fail "out"))
(mapcar #'nix-compile-file
;; These forms where inserted by the Nix build
'(${
lib.concatMapStrings (src: ''
"${src}"
'') srcs
})))
'';
genDumpLisp = { name, main, deps }: writeText "ccl-dump.lisp" ''
${impls.ccl.genLoadLisp deps}
(let* ((out (or (getenv "out") (error "Not running in a Nix build")))
(bindir (concatenate 'string out "/bin/"))
(executable (make-pathname :directory bindir :name "${name}")))
;; Tell UIOP that argv[0] will refer to running image, not the lisp impl
(when (find-package :uiop)
(eval `(setf ,(find-symbol "*IMAGE-DUMPED-P*" :uiop) :executable)))
(save-application executable
:purify t
:error-handler :quit
:toplevel-function
(lambda ()
;; Filter out everything prior to the `--` we
;; insert in the wrapper to prevent SBCL from
;; parsing arguments at startup
(setf ccl:*command-line-argument-list*
(delete "--" ccl:*command-line-argument-list*
:test #'string= :count 1))
(${main}))
:mode #o755
;; TODO(sterni): use :native t on macOS
:prepend-kernel t))
'';
wrapProgram = true;
genTestLisp = genTestLispGeneric impls.ccl;
lispWith = deps:
let lispDeps = filter (d: !d.lispBinary) (allDeps impls.ccl deps);
in writeShellScriptBin "ccl" ''
export LD_LIBRARY_PATH="${lib.makeLibraryPath (allNative [] lispDeps)}"
exec ${ccl}/bin/ccl ${
lib.optionalString (deps != [])
"--load ${writeText "load.lisp" (impls.ccl.genLoadLisp lispDeps)}"
} "$@"
'';
};
};
# #
# Public API functions # Public API functions
# #
# 'library' builds a list of Common Lisp files into a single FASL # 'library' builds a list of Common Lisp files into an implementation
# which can then be loaded into SBCL. # specific library format, usually a single FASL file, which can then be
# loaded and built into an executable via 'program'.
library = library =
{ name { name
, implementation ? defaultImplementation
, brokenOn ? [ ] # TODO(sterni): make this a warning
, srcs , srcs
, deps ? [] , deps ? [ ]
, native ? [] , native ? [ ]
, tests ? null , tests ? null
, passthru ? { }
}: }:
let let
lispNativeDeps = (allNative native deps); filteredDeps = implFilter implementation deps;
lispDeps = allDeps deps; filteredSrcs = implFilter implementation srcs;
testDrv = if ! isNull tests lispNativeDeps = (allNative native filteredDeps);
then testSuite { lispDeps = allDeps implementation filteredDeps;
name = tests.name or "${name}-test"; testDrv =
srcs = srcs ++ (tests.srcs or []); if ! isNull tests
deps = deps ++ (tests.deps or []); then
expression = tests.expression; testSuite
} {
name = tests.name or "${name}-test";
srcs = filteredSrcs ++ (tests.srcs or [ ]);
deps = filteredDeps ++ (tests.deps or [ ]);
expression = tests.expression;
inherit implementation;
}
else null; else null;
in lib.fix (self: runCommandNoCC "${name}-cllib" { in
LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps; lib.fix (self: runCommandNoCC "${name}-cllib"
LANG = "C.UTF-8"; {
} '' LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps;
LANG = "C.UTF-8";
passthru = passthru // {
inherit lispNativeDeps lispDeps;
lispName = name;
lispBinary = false;
tests = testDrv;
};
} ''
${if ! isNull testDrv ${if ! isNull testDrv
then "echo 'Test ${testDrv} succeeded'" then "echo 'Test ${testDrv} succeeded'"
else "echo 'No tests run'"} else "echo 'No tests run'"}
${sbcl}/bin/sbcl --script ${genCompileLisp srcs lispDeps}
echo "Compilation finished, assembling FASL files"
# FASL files can be combined by simply concatenating them
# together, but it needs to be in the compilation order.
mkdir $out mkdir $out
chmod +x cat_fasls ${implementation.runScript} ${
./cat_fasls > $out/${name}.fasl implementation.genCompileLisp {
'' // { srcs = filteredSrcs;
inherit lispNativeDeps lispDeps; inherit name;
lispName = name; deps = lispDeps;
lispBinary = false; }
tests = testDrv; }
sbcl = sbclWith [ self ]; '');
});
# 'program' creates an executable containing a dumped image of the # 'program' creates an executable, usually containing a dumped image of the
# specified sources and dependencies. # specified sources and dependencies.
program = program =
{ name { name
, implementation ? defaultImplementation
, brokenOn ? [ ] # TODO(sterni): make this a warning
, main ? "${name}:main" , main ? "${name}:main"
, srcs , srcs
, deps ? [] , deps ? [ ]
, native ? [] , native ? [ ]
, tests ? null , tests ? null
, passthru ? { }
}: }:
let let
lispDeps = allDeps deps; filteredSrcs = implFilter implementation srcs;
filteredDeps = implFilter implementation deps;
lispDeps = allDeps implementation filteredDeps;
libPath = lib.makeLibraryPath (allNative native lispDeps); libPath = lib.makeLibraryPath (allNative native lispDeps);
selfLib = library { # overriding is used internally to propagate the implementation to use
inherit name srcs native; selfLib = (makeOverridable library) {
inherit name native brokenOn;
deps = lispDeps; deps = lispDeps;
srcs = filteredSrcs;
}; };
testDrv = if ! isNull tests testDrv =
then testSuite { if ! isNull tests
name = tests.name or "${name}-test"; then
srcs = testSuite
( {
srcs ++ (tests.srcs or [])); name = tests.name or "${name}-test";
deps = deps ++ (tests.deps or []); srcs =
expression = tests.expression; (
} # testSuite does run implFilter as well
filteredSrcs ++ (tests.srcs or [ ])
);
deps = filteredDeps ++ (tests.deps or [ ]);
expression = tests.expression;
inherit implementation;
}
else null; else null;
in lib.fix (self: runCommandNoCC "${name}" { in
nativeBuildInputs = [ makeWrapper ]; lib.fix (self: runCommandNoCC "${name}"
LD_LIBRARY_PATH = libPath; {
LANG = "C.UTF-8"; nativeBuildInputs = [ makeWrapper ];
} '' LD_LIBRARY_PATH = libPath;
${if ! isNull testDrv LANG = "C.UTF-8";
then "echo 'Test ${testDrv} succeeded'" passthru = passthru // {
else ""} lispName = name;
mkdir -p $out/bin lispDeps = [ selfLib ];
lispNativeDeps = native;
${sbcl}/bin/sbcl --script ${ lispBinary = true;
genDumpLisp name main ([ selfLib ] ++ lispDeps) tests = testDrv;
};
} }
(''
${if ! isNull testDrv
then "echo 'Test ${testDrv} succeeded'"
else ""}
mkdir -p $out/bin
wrapProgram $out/bin/${name} --prefix LD_LIBRARY_PATH : "${libPath}" ${implementation.runScript} ${
'' // { implementation.genDumpLisp {
lispName = name; inherit name main;
lispDeps = [ selfLib ] ++ (tests.deps or []); deps = ([ selfLib ] ++ lispDeps);
lispNativeDeps = native; }
lispBinary = true; }
tests = testDrv; '' + lib.optionalString implementation.wrapProgram ''
sbcl = sbclWith [ self ]; wrapProgram $out/bin/${name} \
}); --prefix LD_LIBRARY_PATH : "${libPath}" \
--add-flags "\$NIX_BUILDLISP_LISP_ARGS --"
''));
# 'bundled' creates a "library" that calls 'require' on a built-in # 'bundled' creates a "library" which makes a built-in package available,
# package, such as any of SBCL's sb-* packages. # such as any of SBCL's sb-* packages or ASDF. By default this is done
bundled = name: (makeOverridable library) { # by calling 'require', but implementations are free to provide their
inherit name; # own specific bundled function.
srcs = lib.singleton (builtins.toFile "${name}.lisp" "(require '${name})"); bundled = name:
}; let
# TODO(sterni): allow overriding args to underlying 'library' (e. g. srcs)
defaultBundled = implementation: name: library {
inherit name implementation;
srcs = lib.singleton (builtins.toFile "${name}.lisp" "(require '${name})");
};
bundled' =
{ implementation ? defaultImplementation
, name
}:
implementation.bundled or (defaultBundled implementation) name;
in
(makeOverridable bundled') {
inherit name;
};
in
{
library = withExtras library;
program = withExtras program;
inherit bundled;
# 'sbclWith' creates an image with the specified libraries / # 'sbclWith' creates an image with the specified libraries /
# programs loaded. # programs loaded in SBCL.
sbclWith = deps: sbclWith = impls.sbcl.lispWith;
let lispDeps = filter (d: !d.lispBinary) (allDeps deps);
in writeShellScriptBin "sbcl" '' inherit (impls)
export LD_LIBRARY_PATH=${lib.makeLibraryPath (allNative [] lispDeps)}; sbcl
exec ${sbcl}/bin/sbcl ${lib.optionalString (deps != []) "--load ${writeText "load.lisp" (genLoadLisp lispDeps)}"} $@ ecl
''; ccl
in { ;
library = makeOverridable library;
program = makeOverridable program;
sbclWith = makeOverridable sbclWith;
bundled = makeOverridable bundled;
} }

View file

@ -14,15 +14,16 @@ let
]; ];
}; };
# Example Lisp program. # Example Lisp program.
# #
# This builds & writes an executable for a program using the library # This builds & writes an executable for a program using the library
# above to disk. # above to disk.
# #
# By default, buildLisp.program expects the entry point to be # By default, buildLisp.program expects the entry point to be
# `$name:main`. This can be overridden by configuring the `main` # `$name:main`. This can be overridden by configuring the `main`
# attribute. # attribute.
in buildLisp.program { in
buildLisp.program {
name = "example"; name = "example";
deps = [ libExample ]; deps = [ libExample ];

View file

@ -0,0 +1,36 @@
{ depot, pkgs, ... }:
depot.nix.buildLisp.program {
name = "argv0-test";
srcs = [
(pkgs.writeText "argv0-test.lisp" ''
(defpackage :argv0-test (:use :common-lisp :uiop) (:export :main))
(in-package :argv0-test)
(defun main ()
(format t "~A~%" (uiop:argv0)))
'')
];
deps = [
{
sbcl = depot.nix.buildLisp.bundled "uiop";
default = depot.nix.buildLisp.bundled "asdf";
}
];
passthru.meta.ci = {
extraSteps.verify = {
label = "verify argv[0] output";
needsOutput = true;
command = pkgs.writeShellScript "check-argv0" ''
set -eux
for invocation in "$(pwd)/result/bin/argv0-test" "./result/bin/argv0-test"; do
test "$invocation" = "$("$invocation")"
done
'';
};
};
}

View file

@ -0,0 +1,3 @@
inherited: true
owners:
- sterni

View file

@ -0,0 +1,103 @@
{ depot, pkgs, lib, ... }:
let
inherit (pkgs)
gzip
mandoc
coreutils
;
inherit (depot.nix)
runExecline
getBins
;
bins = getBins mandoc [ "mandoc" ]
// getBins gzip [ "gzip" ]
// getBins coreutils [ "mkdir" "ln" "cp" ]
;
defaultGzip = true;
basename = gzip: { name, section, ... }:
"${name}.${toString section}${lib.optionalString gzip ".gz"}";
manDir = { section, ... }:
"\${out}/share/man/man${toString section}";
target = gzip: args:
"${manDir args}/${basename gzip args}";
buildManPage =
{ requireLint ? false
, gzip ? defaultGzip
, ...
}:
{ content
, ...
}@page:
let
source = builtins.toFile (basename false page) content;
in
runExecline (basename gzip page) { } ([
(if requireLint then "if" else "foreground")
[
bins.mandoc
"-mdoc"
"-T"
"lint"
source
]
"importas"
"out"
"out"
] ++ (if gzip then [
"redirfd"
"-w"
"1"
"$out"
bins.gzip
"-c"
source
] else [
bins.cp
"--reflink=auto"
source
"$out"
]));
buildManPages =
name:
{ derivationArgs ? { }
, gzip ? defaultGzip
, ...
}@args:
pages:
runExecline "${name}-man-pages"
{
inherit derivationArgs;
}
([
"importas"
"out"
"out"
] ++ lib.concatMap
({ name, section, content }@page: [
"if"
[ bins.mkdir "-p" (manDir page) ]
"if"
[
bins.ln
"-s"
(buildManPage args page)
(target gzip page)
]
])
pages);
in
{
__functor = _: buildManPages;
single = buildManPage;
}

View file

@ -1,34 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
#
# A crude wrapper around //nix/buildGo that supports the Go 2 alpha.
#
# The way the alpha is implemented is via a transpiler from typed to
# untyped Go.
{ depot, pkgs, ... }:
let
inherit (builtins)
baseNameOf
stringLength
substring;
inherit (depot.nix.buildGo) gpackage program;
go2goext = file: substring 0 ((stringLength file) - 1) file;
go2go = file: pkgs.runCommandNoCC "${go2goext (baseNameOf file)}" {} ''
cp ${file} .
${pkgs.go}/bin/go tool go2go translate *.go2
mv *.go $out
'';
in rec {
program = { name, srcs, deps ? [], x_defs ? {} }: depot.nix.buildGo.program {
inherit name deps x_defs;
srcs = map go2go srcs;
};
package = { name, srcs, deps ? [], path ? name, sfiles ? [] }: depot.nix.buildGo.package {
inherit name deps path sfiles;
srcs = map go2go srcs;
};
}

Some files were not shown because too many files have changed in this diff Show more