First, you must already have Git installed, to clone this repository. The recommended way to do so is via the GitHub CLI; once you have that installed and set up, run this command:
gh repo clone penrose/penroseThen, enter the directory holding the repository clone you just made:
cd penroseThe rest of this document assumes you are running commands from this directory, unless otherwise specified.
Building Penrose from source requires you to have specific versions of a few other pieces of software installed. Speaking from experience, getting the right versions of everything here is difficult, so you may want to consider using Nix to guarantee that you have the right versions of everything. However, Nix is definitely not for everyone, so by all means feel free to use the Without Nix section instead to install everything manually.
First, install Nix if you haven't already. Then, enable flakes. Probably the
easiest way to do that is to put this line in the file ~/.config/nix/nix.conf:
experimental-features = nix-command flakes
If you're new to Nix, that file probably doesn't exist yet, so you can create it with those contents by just running this command:
echo 'experimental-features = nix-command flakes' > ~/.config/nix/nix.confNow, you could run the following command to create a nested shell within your existing terminal, which will have all the software you need available:
nix developYou can use exit at any time to go back out of the nested shell to your
original one. If you want, you can stop right here and skip to the Finishing
setup section; or if you want a bit more developer
convenience then keep reading.
Running nix develop does work, but then you'll only be able to have the
software installed in terminals where you've run nix develop. A more automatic
approach is to install direnv, which will let you automatically put all the
right programs into your existing terminal whenever you enter this directory,
and take them out whenever you leave.
If you're still in a nix develop shell, go ahead and exit it. Then once
you've installed direnv, you can create the appropriate .envrc file in this
directory by running this command:
echo 'use flake' > .envrcThat tells direnv that you want it to use the flake.nix file in
this repository. Then, run the following command to tell direnv that the setup
in this repository is safe to use:
direnv allowYou should see some output as direnv sets up the environment, and then the right versions of everything should be available in your terminal!
Now if you leave this directory e.g. by running cd .., you'll see a message
saying direnv: unloading and all the programs listed in flake.nix will
disappear. If you enter again via cd penrose, you'll see a longer message
starting with direnv: loading and all the programs will be available again.
If you're a VS Code user, you're highly recommended to also install the direnv
VS Code extension, which allows all your other VS Code extensions to also
pick up on the programs provided by flake.nix and your .envrc.
If you're not using Nix, be sure to have these tools installed:
-
Python 3.10 or below (if using Linux or Mac, we recommend installing via Pyenv)
-
Node.js v18 (if using Linux or Mac, we recommend installing via nvm)
- Yarn v1.x
And then depending on your platform, here are some extra instructions:
If you're using an ARM-based Mac, node-canvas (one of our dependencies) also requires some additional packages to be installed. Install Homebrew if you don't already have it, then run this command:
brew install pkg-config cairo pango libpng jpeg giflib librsvg pixmanNote that, because some of these packages depend on Python, your Python version
may be wrong after this step even if it was correct earlier; for instance, if
you've never installed Python before so you just have the Python 3.9 that your
Mac comes preinstalled with, you may think you're OK but then after this step
you may run python3 --version again and find that you now have Python 3.12
installed. Using Pyenv is a great way to prevent these shenanigans from
messing with your Penrose setup!
Here are some WSL-specific guides:
Once you've gotten all the right software (whether via Nix or otherwise), there are only a couple setup steps remaining. Install dependencies from npm:
yarnThen, build the dependencies of our roger tool.
yarn build:rogerFinally, enter the directory of roger to install it as a global binary
executable, and then come back to this directory:
pushd packages/roger/
npm link
popdOpen a separate terminal (to the same directory), and run these commands:
cd packages/examples/
roger watchYou should see this output:
watching on port 9160
Then, back in your original terminal, run this command:
yarn startOnce it finishes building, you should see this near the end of the output:
> Local: http://localhost:3000/try/
> Network: use `--host` to expose
Click that link. The page may take some time to load, but once it does, you should see something like this:
Type in the drop-down boxes to search for any Penrose trio in
packages/examples/; for example:
- Substance:
src/set-theory-domain/tree.substance - Style:
src/set-theory-domain/euler.style - Domain:
src/set-theory-domain/setTheory.domain
... and voilà! ✨ See the results in your browser:
If you run yarn start, open the browser, and see a blank page with a browser console error message along the lines of The requested module ... penrose_optimizer.js ... does not provide an export named ..., this appears to be a dependency re-optimization issue. Try running with
yarn start --forcein this case.
Run this command to build all packages for production:
yarn buildRun this command to typecheck all packages:
yarn typecheckWe have a packages/examples/src/registry.json file which lists several
diagrams from the packages/examples/src/ directory. All the trios listed in
this file are automatically run in GitHub Actions to produce the SVG files in
the ci/* branches.
If you create a new diagram in packages/examples/src/ and you'd like to make
sure that future changes to Penrose don't inadvertently break your diagram, go
ahead add it to the registry! For instance, let's say you create this directory
under packages/examples/src/:
packages/examples/src/foo-domain/
├── foo.domain
├── bar.style
└── baz.substance
The first step in adding this to the registry is to create a trio JSON file; if
you find that these give a nice diagram using variation CedarEagle308, put
this in packages/examples/src/foo-domain/example.trio.json:
{
"domain": "./foo.domain",
"style": ["./bar.style"],
"substance": "./baz.substance",
"variation": "CedarEagle308"
}The add the following to packages/examples/src/registry.json. By default,
examples won't show up in @penrose/editor. Setting "gallery": true will add
your example to the example gallery in editor:
"foo-domain/example": {
"name": "My Trio",
"gallery": true
}And you're done!
To render all diagrams in the registry, run yarn registry in the repo root. The output SVGs will be saved under packages/examples/diagrams.
To delete all build artifacts (but no node_modules/):
git clean -dfxe node_modules/To delete node_modules/ (but not build artifacts) in all packages/:
yarn lerna cleanTo delete the node_modules/ at the repo root:
npx rimraf node_modules/To do all of the above at once:
git clean -dfxUnder the hood, roger is an executable script (packages/roger/bin/run.js), which is symlinked by npm link. If roger is not working as expected and you think it might be out of date, run
these commands to re-install it:
pushd packages/roger/
npm unlink --global @penrose/roger
npm link
popdyarn also provides a link functionality but we favor npm link because of known issues with yarn link.
To run all tests:
yarn testTo automatically re-run tests as you make changes to core:
npx nx run core:test-watchThe CI process runs a test that checks whether the set-euler-diagram example generates the same image that goes onto the README page. If you made changes to Penrose, it might generate something different from the image on the README page. If this is expected, update the README image by doing the following:
- Build
@penrose/roger - Run
.github/gen_readme.js
This should update the image that is placed onto the README page.
To add a project dependency to, e.g., browser-ui (note, we don't use npm):
pushd packages/browser-ui/
yarn add $DEPENDENCY_NAME
popdTo add a dev dependency:
pushd packages/$PACKAGE_NAME/
yarn add --dev $DEPENDENCY_NAME
popdIf you're using a package that involves the DOM, you probably want the react
version (e.g. react-graph-vis instead of visjs).
We use Nx to manage dependencies among our various scripts/tasks, which uses
metadata in all our package/*/package.json files. Specifically, below the
"scripts" section we usually have a "nx" section defining metadata about
each script. When you update a script, be sure to update its accompanying
metadata!
The "targetDefaults" part of nx.json defines default dependencies for some
scripts for which we use the same semantics across all our packages:
buildmeans to produce executable artifacts (usually JavaScript files), and should depend on thebuildscripts of that package's dependenciesbuild-declsmeans to produce TypeScript declaration files, and should depend on thebuild-declsscripts of that package's dependenciestypecheckmeans to check for type errors in the package, and should depend on thebuild-declsscript of that same package (with the intention being that for any given package you either write abuild-declsscript or atypecheckscript, but not both)
Some packages do not need to do anything for one or more of these scripts. In
that case, the script should still be present in the "scripts" section of that
package's package.json file, so that Nx doesn't break the dependency chain; in
this case, the contents of the script should just be ":".
To import a type or function from core in another package like browser-ui,
import the type into packages/core/src/index.ts and export it from there
again, then import into your project.
If you'd like to make a change and contribute it back to the project, but you don't have write permissions to this repository, you'll need to create a fork. Click the Fork button in the top-right corner of this page.
You should already have a clone of this repo by following the instructions at the start of this document, so now you simply need to add your fork as another remote:
git remote add fork https://github.com/<your-github-account-name>/penrose.gitCheck out our list of good first issues.
-
Before working on one of them, let us know that you are interested so we can give you more guidance! (Currently the issue descriptions are fairly brief.)
-
Create a separate branch in your forked repo to work on the issue:
git switch --create my-branch git push --set-upstream fork my-branch
If you need to merge new changes from upstream (i.e. the original Penrose repo):
git fetch origin main:main
git merge mainAfter running the above, manage any merge conflicts, commit to your branch, and then push to your fork:
git pushFor some PRs, it can be helpful to add tests that help verify the correctness of new features, and which ensure features don't break in future versions. Tests can be created as example diagrams in packages/examples/src and added to the registry.
When your work is ready for review:
- Open a pull request (PR) by clicking on the Contribute button on the
homepage of your forked repo
(
https://github.com/<your-github-account-name>/penrose). - Put
fix:orfeat:at the beginning of the PR title depending on if it's a fix or a feature. We follow conventional commit guidelines in our repo. - Document your changes in the PR's description (including specific paths for reproducing specific examples, and link(s) to any issue(s) you address).
- Some things will be checked automatically by our CI:
- Make sure the system passes the regression tests.
- Run Prettier via
yarn format.
- If you have permission, request review from the relevant person. Otherwise, no worries: we'll take a look at your PR and assign it to a maintainer.
- When your PR is approved, a maintainer will merge it.
If you hit any snags in the process, run into bugs, or just have questions, please file an issue!
Our repo uses semantic versioning and maintains the same version number for all packages. Generally speaking, we release new versions whenever new features are introduced (PRs with feat tag). Here are the steps for creating new releases.
- Make sure all PRs for the upcoming release are merged. Switch to
mainand checkgit statusto make sure it's clean and up-to-date. - At repo root, run
yarn new-versionto create a new version. - Run
yarn formatto clean up auto-generated file changes. - Create a new branch (
git switch --create release-X.Y.Z) from main and commit the changes. - Open a new PR with a title
chore: bump version to X.Y.Zand merge after CI passes. - Create a new GitHub release.
- CI will run after the new release is created, automatically publishing packages to npm.
To publish development releases:
- Make sure all PRs for the upcoming release are merged. Switch to
mainand checkgit statusto make sure it's clean and up-to-date. - Create the pre-release version without tagging or committing:
yarn lerna version --conventional-commits --conventional-prerelease --no-git-tag-version --no-push- Run
yarn formatto clean up auto-generated file changes. - Create a new branch (
git switch --create release-X.Y.Z) from main and commit the changes. - Publish the pre-release version:
yarn lerna publish from-package --pre-dist-tag develop
- Open a new PR with a title
chore: bump version to X.Y.Zand merge after CI passes.

