Start writing tutorial

This commit is contained in:
Radon Rosborough 2021-02-06 21:29:16 -08:00
parent 82c7f6d4af
commit a2d889860d
4 changed files with 365 additions and 0 deletions

View File

@ -1 +1,133 @@
# Adding your own language to Riju
Hello and welcome! This tutorial guides you through the basics of
adding a new language to Riju, or modifying an existing language. The
other documentation in this repo has reference material that may be
helpful for advanced use cases, but this page should get you started.
If you run into any trouble following the guide, please do not
hesitate to open an issue!
## Project setup
Fork this repository to your account on GitHub, and clone it locally:
```
$ git clone https://github.com/yourname/riju.git
$ cd riju
```
Install [Docker](https://www.docker.com/). Then you can build and
start the admin shell:
```
$ make image shell I=admin
```
All future operations can be done inside the admin shell, where all
dependencies are installed automatically.
## Start tmux
Start a tmux session:
```
$ make tmux
```
If you don't know how to use tmux, see [a
cheatsheet](https://danielmiessler.com/study/tmux/). The useful
keybindings are:
* `control-b c`: open new tab
* `control-b p/n`: previous/next tab
* `control-b "`: split tab into top and bottom panes
* `control-b %`: split tab into left and right panes
* `control-b <arrows>`: move between panes
* `control-b control-b <something>`: if you have two tmuxes nested,
use `control-b` twice to do a command on the inner one instead of
the outer one
## Configure local project
Using your regular text editor (the Riju repository is synchronized
inside and outside of the container, so you can use whatever editor
you would like, it doesn't have to be something in the terminal),
create a file `.env` in the Riju repository with the following
contents:
```
DOCKER_REPO=raxod502/riju
S3_BUCKET=riju
```
This tells Riju to pull assets from the official registries that I
maintain, so that you don't have to build them yourself.
## Set up Docker images
Download the two Docker images needed for testing a new language:
```
$ make pull I=packaging
$ make pull I=runtime
```
Create a new tab in tmux (`control-b c`) and start the runtime image
with ports exposed:
```
$ make shell I=runtime E=1
```
Inside that shell, start another instance of tmux:
```
$ make tmux
```
Now within that tmux, start Riju in development mode:
```
$ make dev
```
You should now be able to navigate to <http://localhost:6119> and see
that Riju is running, although it does not have any languages
installed.
Finally, switch back to the admin shell (`control-b p`). We are ready
to start creating your new language.
## Create a language configuration
Create a file `langs/mylanguage.yaml` with the following contents:
```yaml
id: "mylanguage"
name: "My Language"
main: "TODO"
template: |
# Fill this in later
run: |
echo "Hello, world!"
```
Now from the admin shell, run `make repkgs L=mylanguage`. Once that
completes, you should see your language at <http://localhost:6119>.
Furthermore, you can switch to the runtime image (`control-b n`) and
run `make sandbox L=mylanguage` to test your language at the command
line (e.g. type `run` to print `Hello, world!`). Each time you modify
the language configuration, run `make repkgs` to reinstall the
language.
Follow these steps to augment your language configuration:
* [Install your language](tutorial/install.md)
* [Provide run commands](tutorial/run.md)
* [Configure tests](tutorial/tests.md)
* [Provide metadata](tutorial/metadata.md)
* [Add code formatter](tutorial/formatter.md)
* [Add language server](tutorial/lsp.md)

View File

232
doc/tutorial/install.md Normal file
View File

@ -0,0 +1,232 @@
# Tutorial: install your language
Presumably, your language isn't installed by default in Ubuntu. If
not, you'll need to add an `install:` block to your language
configuration describing how to install it.
The easiest case is system packages, which are supported out of the
box, for example:
```yaml
install:
apt:
- ruby
npm:
- pug-cli
pip:
- bython
gem:
- solargraph
cpan:
- Acme::Chef
opam:
- ocamlformat
```
For more advanced configuration you need to understand how Debian
packaging works. To build a package, we place files into a `${pkg}`
directory mirroring how they should be installed on the system, so for
example a binary intended for `/usr/local/bin/prettier` would be
placed at `${pkg}/usr/local/bin/prettier`. Then the `${pkg}` directory
can be turned into a `.deb`, which can be installed on any system.
*If you have trouble, see the tutorial on [debugging package
installation](install-debugging.md).*
## Download a binary or script
We prefer to put all binaries in `/usr/local/bin` rather than
`/usr/bin` where they might conflict with default Ubuntu packages.
```yaml
install:
manual: |
install -d "${pkg}/usr/local/bin"
wget http://www.blue.sky.or.jp/grass/grass.rb
chmod +x grass.rb
cp -T grass.rb "${pkg}/usr/local/bin/grass"
```
## Get the latest version from GitHub
There is a predefined `latest_release` function that can be used to
get the name of the latest tag in a GitHub repository.
```yaml
install:
manual: |
install -d "${pkg}/usr/local/bin"
ver="$(latest_release snoe/clojure-lsp)"
wget "https://github.com/snoe/clojure-lsp/releases/download/${ver}/clojure-lsp"
chmod +x clojure-lsp
cp clojure-lsp "${pkg}/usr/local/bin/"
```
## Get the latest version from some other website
In the case that there's no "download the latest version" URL, we
typically use disgusting `grep` pipelines to extract it in a
semi-reliable way from a stable-seeming webpage.
```yaml
install:
manual: |
install -d "${pkg}/usr/local/bin"
path="$(curl -fsSL http://static.red-lang.org/download.html | grep -Eo '/dl/linux/[^"]+' | head -n1)"
wget "http://static.red-lang.org${path}" -O red
chmod +x red
cp red "${pkg}/usr/local/bin/"
```
## Unpack a tarball
Sometimes tarballs can be extracted directly to `/usr/local`. However,
unless there's an obviously correct way to do that, we typically put
language distributions in `/opt/<lang>` and then put symlinks in
`/usr/local/bin` for the binaries. The `-C` and `--strip-components`
flags are very helpful for controlling the extraction.
Also, one thing that might be unintuitive is that we use system-global
paths for the targets of symlinks (`/opt/swift/bin/swiftc`) but
`${pkg}` paths for the link names (`${pkg}/usr/local/bin/swiftc`).
This is because while we are putting all files into `${pkg}` during
build, the eventual place they will be installed by the package is
into the root filesystem, so any references to paths *within* files
(including symlink targets) must not mention `${pkg}`.
```yaml
install:
manual: |
install -d "${pkg}/opt/swift"
install -d "${pkg}/usr/local/bin"
ver="$(latest_release apple/swift | grep -Eo '[0-9.]+')"
wget "https://swift.org/builds/swift-${ver}-release/ubuntu2004/swift-${ver}-RELEASE/swift-${ver}-RELEASE-ubuntu20.04.tar.gz" -O swift.tar.gz
tar -xf swift.tar.gz -C "${pkg}/opt/swift" --strip-components=2
ln -s /opt/swift/bin/swiftc /opt/swift/bin/sourcekit-lsp "${pkg}/usr/local/bin/"
```
## Compile from source
Sometimes there is no binary distribution. In this case you want to
clone/download the source code and run the appropriate compilation
command. GCC and LLVM plus a number of scripting languages are already
installed into the `packaging` image by default, but you might have to
include a `prepare` block listing additional things to install to be
able to compile the software.
For example, in this case the `build.sh` script provided by Zot
happens to require some Qt development dependencies at compile time,
and a non-development dependency at runtime.
```yaml
install:
prepare:
apt:
- qt5-qmake
- qtscript5-dev
apt:
- libqt5script5
manual: |
install -d "${pkg}/usr/local/bin"
git clone https://github.com/manyoso/zot.git
pushd zot
./build.sh
cp build/bin/zot "${pkg}/usr/local/bin/"
popd
```
## Use a shared dependency
Your language may need to use software such as Prettier which is also
used by other languages but does not have an Ubuntu package. In that
case we package it as a Riju shared dependency (check the `shared`
directory for a list), and you declare the dependency using the `riju`
key.
```yaml
install:
riju:
- prettier
```
## Install custom scripts or config files
Sometimes you may need to add a wrapper script or config file
somewhere on the filesystem. Riju has the `scripts` and `files` keys
for this. The keys of these maps are the filesystem paths (`scripts`
defaults to putting things in `/usr/local/bin` if relative paths are
given).
```yaml
install:
scripts:
teco-encode: |
#!/usr/bin/env -S python3 -u
import re
import sys
for line in sys.stdin:
line = re.sub(r"\^(.)", lambda m: chr(ord(m.group(1)) ^ 0b1000000), line)
line = line.replace("$", chr(27))
print(line, end="")
files:
"/opt/sqlite/sqls.yml": |
connections:
- driver: sqlite3
dataSourceName: db.sqlite3
```
## Setting up skeleton files
Sometimes your language may require some configuration files in
addition to the actual file to be compiled. Or alternatively, your
language may do a bunch of caching work the first time it starts up,
which you don't want to happen every time someone launches it on Riju.
Both of these problems can be solved by preparing files to be copied
into the user's home directory at the start of a new Riju session
(this is done using the `setup` key, which is explained later).
In the following example, we ask .NET to create a project template for
us, and then compile the project. Then we take the files that were
created for the project, plus cache directories in `$HOME`, and copy
them into `/opt` so that they can be copied back later using `setup`.
By convention, we name such directories `skel`.
```yaml
install:
prepare:
apt:
- $(grep-aptavail -wF Package "dotnet-sdk-3\.[0-9.]+" -s Package -n | sort -Vr | head -n1)
manual: |
install -d "${pkg}/opt/qsharp/skel"
dotnet new -i Microsoft.Quantum.ProjectTemplates
dotnet new console -lang Q# -o main
dotnet run --project main
shopt -s dotglob
cp -R * "${HOME}/.dotnet" "${HOME}/.nuget" "${pkg}/opt/qsharp/skel/"
rm "${pkg}/opt/qsharp/skel/main/Program.qs"
chmod -R a=u,go-w "${pkg}/opt/qsharp/skel"
```
## Dealing with versioned APT packages
For some reason, some APT packages have version numbers in their
names. For example, you can't `sudo apt install lua`; you have to
`sudo apt install lua5.4`. The best way to deal with these situations
is to use various `grep-aptavail` hacks to identify the latest
available version programmatically. Check the man page as well as uses
of `grep-aptavail` in Riju to understand the options.
```yaml
install:
apt:
- $(grep-aptavail -XF Provides lua -a -XF Version "$(grep-aptavail -XF Provides lua -s Version -n | sort -Vr | head -n1)" -s Package -n | head -n1)
```

1
doc/tutorial/run.md Normal file
View File

@ -0,0 +1 @@
# Tutorial: provide run commands