Skip to content
haskell-misoPublic

About

πŸ”‹ miso command line tool: build, install, scaffold, hot reload, etc.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

Β 

History

49 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ”‹ miso

The command line tool for miso apps: an executable with the MicroHs compiler and MicroCabal inside, and miso and its dependencies already installed.

curl -fsSL https://haskell-miso.org/download | sh

It downloads the release for your OS and architecture and checks its sha256. Then it shows what it will create, replace and remove in ~/.miso (or $MISO_HOME) and asks before installing; it lists what it installed, asks to add ~/.miso/bin to PATH in your shell's startup file (.zshrc, .bashrc or .bash_profile, fish's config.fish, or .profile), and checks the installation with miso doctor (which says when emcc, for the browser, is missing). sh -s -- --yes (or -y) does not ask, and --no-modify-path leaves the startup files alone; MISO_VERSION=v0.1.0 installs that release, MISO_HOME installs somewhere else, and MISO_RELEASE_URL downloads from a mirror (or file://). On NixOS it says to install miso with nix instead, and installs nothing (--yes installs in ~/.miso anyway, as does a reinstall or miso upgrade of a miso installed there):

nix profile install github:haskell-miso/cli#release   # the release, prebuilt
nix profile install github:haskell-miso/cli           # built from source

#release is the tarball of the latest GitHub release (the one install.sh downloads), pinned with its sha256 in nix/release.json, which the release workflow updates on main after each release. Until the first release, nix/release.json is empty and there is no #release.

miso new counter      # a new project: miso.yml, app/Main.hs, static/index.html,
                      # .gitignore and README.md
cd counter
miso build            # public/index.js (target: browser)

Commands

docs/manual.md describes each command in full: its arguments, what it writes, and when it fails. Commands that work on a project read miso.yml in the current directory (not in a parent).

command
miso new NAME [--template T] a new project in the directory NAME (which must not exist), from a template (default counter); miso templates lists them (counter, router, canvas). miso new --template T NAME works too
miso init make the current directory a project. On a terminal it asks for the name (default: the directory's), version, synopsis, author (from git), license and template, one at a time; --name, --version, --synopsis, --author, --license and --template answer them, and --yes (or input that is not a terminal) takes the defaults. It writes the template's files that are not there yet (a .gitignore gets the lines it lacks), and a LICENSE for BSD-3-Clause, BSD-2-Clause and MIT; license none writes no license:. It fails where there is a miso.yml already
miso build [NAME...] install missing dependencies, type check the library, and build every program of miso.yml (or only the programs named, without the library). It checks for emcc first when a program needs it. On a terminal it shows the last lines of the build as it runs, as miso dev does, then what mhs said (warnings, or the error; the whole log is .miso-build.log)
miso run [NAME] [ARGS] build and run a program (target: native; the first one, or NAME)
miso test [NAME...] [-- ARGS] build the tests of miso.yml (or those named) and run each, with ARGS; it fails when a test does
miso dev [NAME] [--port N] build a browser program (the first one, or NAME), serve public/, and on a change of the sources rebuild and reload the page
miso serve [--port N] serve public/ (on the first free port from 8000, or N)
miso watch [NAME] type check the library, the programs and the tests (or NAME) on every change, and show the first error, as ghcid does
miso lint [ARGS] run hlint on the source dirs of miso.yml, with ARGS; it reads .hlint.yaml
miso repl mhs, interactively: in a project with its sources, mhs-options and packages (installed first), elsewhere with the tool's packages. Input that is not a terminal is read as lines, so expressions can be piped to it
miso clean in a project only: remove dist/, public/, .miso/, .mhscache, dist-mcabal/ and .miso-build.log (not .miso/ when it is $MISO_HOME)
miso install install the dependencies of miso.yml, in the project's .miso/
miso add PKG... add Hackage packages to the dependencies of miso.yml (keeping its comments), and install them; when the install fails, miso.yml is left as it was
miso remove PKG... remove dependencies from miso.yml; their packages stay in .miso/ until miso clean
miso list the packages: those that come with miso, and in a project the ones it installed in .miso/
miso lock write miso.lock: the dependencies of miso.yml and the packages they need; lock what is not locked yet, keep what is
miso update a new package list for mcabal, and miso.lock again at its versions (git dependencies at their current commits); then install
miso vendor lock as miso lock does, and copy the source of each locked dependency to vendor/NAME; builds then install from there, and fail rather than download
miso cabal print the .cabal file of the project's library: what another project that depends on it builds it with
miso doctor check the installation and the tools miso needs, and say how to fix what is missing
miso upgrade [VERSION] install the latest release (or VERSION) in place
miso uninstall [--yes] remove miso's files: in $MISO_HOME (packages, caches; the caches only when the folder holds nothing else) and the tool where install.sh put it (not one installed with nix). It shows what it removes and asks first; without a terminal it needs --yes. It lists the lines of your shell files that add miso to PATH (it does not edit them)
miso version the version of miso and of MicroHs (also --version)
miso help the commands (also --help, or no command)
miso mhs ARGS / miso mcabal ARGS the MicroHs compiler, and MicroCabal

Output is colored on a terminal; set NO_COLOR=1 to turn that off.

With a MicroHs that has the browser runtime (target browser_rts), miso dev does not run emcc on a save of a target: browser program: mhs writes the program as combinators (app.comb next to index.js in public/, about 3s), and a runtime built once with emcc runs it. The runtime is built for the program's foreign imports and its C and JavaScript files (c-sources, js-sources, include files) with their options, and kept in $MISO_HOME/rts for later builds with the same ones; a new foreign import, or an edit to one of those files, builds another one, once.

miso dev and miso serve use a small server in the binary (cbits/serve.c), on 127.0.0.1. In dev mode HTML pages get a script that polls the server and reloads them when a build succeeds; a failed build keeps the last one.

miso.yml

name: counter
version: 0.1.0             # required: numbers separated by dots
synopsis: A counter        # optional: one line about the package
author: Ada <ada@example.org>   # optional
license: BSD-3-Clause      # optional: an SPDX id
main: Main                 # the main module (default Main)
source-dirs: [app]         # default [app]
target: browser            # browser (WebAssembly) or browser_js (JavaScript), which
                           # need emcc; or native (the default)
mhs-options: []            # passed to mhs
dependencies:
  - split                  # from Hackage (Stackage), with mcabal
  - name: lens-family      # from git, at a revision, with cabal flags
    git: https://github.com/example/lens-family
    rev: v2.1.3
    flags: -foo +bar
  - name: my-lib           # a local checkout, relative to miso.yml
    path: ../my-lib        # (installed again when its files change)
  - name: aeson            # a version from Hackage, not the package list's
    version: 2.2.3.0       # (not for git: or path:, which have their own)

miso reads a subset of YAML: mappings, block lists, [a, b] lists, scalars and comments. A dependency listed in several components must be the same everywhere (list it once, at the top level).

main, source-dirs and target there describe the project's one program. Without target, a program is native. Another target is passed to mhs (-tTARGET); emscripten, emscripten_js, emscripten_web and browser_rts compile with emcc too. A project with a library, or with several programs, has sections for them instead:

name: todo
version: 0.1.0
dependencies: [split]      # for every component
library:                   # code the programs share
  source-dirs: [src]       # default [src]
  exposed-modules: [Todo]  # default: every module under source-dirs
executables:
  web:                     # miso build web, miso dev web, ...
    main: Main
    source-dirs: [app]
    target: browser
  cli:
    main: Cli
    source-dirs: [cli]
    dependencies: [containers]   # added to the project's
  • A program's main defaults to Main, its source-dirs to [app], and its static-dir to static (one that is given must exist). Program and test names, and the name of a project with one program, are a letter, then letters, digits and -.
  • Every program can import the library's modules (mhs gets its source-dirs first). The library and each program may also have dependencies and mhs-options, added to the project's.
  • miso build type checks the library (mhs -fno-code) and builds every program: native ones to dist/NAME, a browser one to public/, or with several browser programs, each to public/NAME/.
  • The top-level main, source-dirs and target (and the fields below) cannot be used with executables:. A project with just library: is a library: miso build checks it, and there is nothing to run.
  • Another project can depend on a project with a library, from git: or a path:, as on any package. miso builds it from a copy (in $MISO_HOME/src/cabalized) with the .cabal file of its library, which miso cabal prints: its name, version, modules, dependencies and the fields below. The checkout itself is not changed. The name: of the dependency is the project's. Its dependencies are by name (from Hackage, or the tool's); one it takes from git: or a path: has to be listed in the project that uses it instead.

Tests are programs that run on this machine, and pass when they exit with 0 (as Cabal's exitcode-stdio tests):

tests:
  spec:                    # miso test spec
    main: Spec
    source-dirs: [test]    # default [test]
    dependencies: [split]  # added to the project's
  • miso test builds every test to dist/NAME, runs each (with the arguments after --), and fails when one of them fails; all of them run. miso build does not build the tests.
  • A test can import the library's modules, has no target or static-dir, and takes mhs-options and the fields below, as a program does. A test and a program cannot have the same name.

The library, each program, and a project with one program also take:

c-sources: [cbits/*.c]       # C files (globs; * and ? in the last part)
js-sources: [jsbits/*.js]    # JavaScript for browser programs (emcc --pre-js)
include-dirs: [include]      # -I for the C files and for CPP
cpp-options: [-DDEBUG]       # for modules with {-# LANGUAGE CPP #-}
extra-libraries: [m]         # -lm for the linker
cc-options: [-DSCALE=2]      # for the C compiler
ld-options: []               # for the linker
static-dir: static           # programs only: copied next to a browser program

Without c-sources, js-sources or include-dirs, cbits/*.c, jsbits/*.js and include/ are used when they are there: the library's, or in a project without one, each program's. What a library has goes into every program. Browser programs are built with WebAssembly's current exception instructions (-sWASM_LEGACY_EXCEPTIONS=0), which browsers keep; the old try instruction is deprecated.

miso watch runs mhs -fno-code (under a second, where a build with emcc takes many) on the library, each program and each test, clears the screen, and shows All good or the first error as file:line:col: message. mhs stops at the first error, so after fixing one, the next shows on the next save.

Packages

Some packages come with the tool, in its package database, next to it (~/.miso/mhs-VERSION when install.sh installed it): the compiler's (base, ghc-compat, array-mhs, template-haskell), and the libraries containers, mtl and transformers.

miso is not one of them: a project depends on it (miso new writes dependencies: [miso]), and locks it like any other package. - miso, with no source of its own, is the miso of the tool's release: the commit of dmjio/miso it was built from (~/.miso/mhs-VERSION/miso-default), locked as a git dependency at that commit, and installed as it came with the release (~/.miso/mhs-VERSION/miso-prebuilt), not built. A new release of the tool does not change the miso of a project that has it in miso.lock; miso update moves it to the new release's. Another miso is a dependency with its own source (git:, path:, or a version: from Hackage). A project whose code imports Miso but does not depend on it (from before) gets the release's, with a warning. The packages a project installs go in the project, in .miso/ (next to miso.yml; miso new puts it in .gitignore, and miso clean removes it). mhs looks for packages in the project's .miso/ first, then in the tool's database, and nowhere else: what one project installs is not seen by another, so two projects can use two versions of a package.

Building a package takes time, so each one is kept in a store, ~/.miso/store/mhs-VERSION/, and a project that needs the same one again copies it from there (Installing split 0.2.5.1 (built already, from the store)). A package is the same one when it has the same source (the sha256 of a Hackage tarball, or a git commit), cabal flags and compiler, and was built against the same packages; one built against a local (path:) package is not kept (it is built again when that changes). The downloads and git checkouts are kept in ~/.miso/src, and mcabal's package list in ~/.miso/mcabal, for every project. A project with no miso.lock gets its dependencies locked for the install, as miso lock would (without writing miso.lock), so they are kept in the store too.

A project can replace a library that comes with the tool: a dependency on it with git:, path:, or a version: that is not the tool's. It is installed in the project's .miso/, and the tool's libraries that need it are built again in the project, from their source (the release ships the source of its libraries, in ~/.miso/mhs-VERSION/miso-sources), against it; miso list shows which:

dependencies:
  - miso                   # built again against transformers 0.6.1.1
  - name: transformers     # instead of the tool's 0.6.1.2: mtl, which needs
    version: 0.6.1.1       # it, is built again against it

miso from a checkout is a path: dependency, with the flags the release builds it with (in ~/.miso/mhs-VERSION/miso-default):

dependencies:
  - name: miso
    path: ../miso
    flags: -template-haskell -aeson -native -production -ssr -benchmark -text

The compiler's packages cannot be replaced (miso build says so). miso.lock records the versions of the tool's libraries a project uses; a newer tool may come with others, and miso build then warns (miso lock records them; to keep one, give its version: in miso.yml).

miso.lock

miso lock and miso update write miso.lock, next to miso.yml; commit it. It has the dependencies of miso.yml and every package they need, in the order they install in (each after the packages it needs): for a package from Hackage, a version and the sha256 of its tarball, and for a git dependency the commit its rev (a tag, a commit, or a branch) is at, and the version of the package there (from its miso.yml, or its .cabal file):

# miso.lock: written by miso lock and miso update.  Commit it; do not edit it.
# Local (path:) dependencies are not locked.
miso: 0.1.0
mhs: 0.16.12.0
bundled: containers-0.8 mtl-2.3.1 transformers-0.6.1.2
packages:
  - name: split
    hackage: 0.2.5.1
    sha256: 18adb60f7f12d08601c382d47c2bacbbd4de4b9233f09683889d15b64b6420d6
  - name: data-default         # needed by data-default-class
    hackage: 0.8.0.2
    sha256: fa7d5de70ccef7381a7f17cd64948de1c24b620de13e1cd55c36c26949870799
  - name: data-default-class
    hackage: 0.2.0.0
    sha256: 0ae530c4fb0bd6c8a8ba399429ccd9c75f9c7696049117178f4ceeb2bd08d5b4
  - name: lens-family
    git: https://github.com/example/lens-family
    rev: v2.1.3
    commit: 8fa9e00cca7db83a3270e12943f1820dad1f2472
    version: 2.1.3
  • The packages a package needs are the build-depends of its library and executables, as mcabal reads them: for this OS and architecture, with the dependency's flags, and with MicroHs's names (random is random-mhs). Their versions are those of mcabal's package list (a Stackage snapshot, from miso update), as mcabal would install them; a package that is not in it gets Hackage's newest version, and miso lock says so. The packages that come with the tool, and those built into MicroHs (base, text, bytestring, ...), are not locked.
  • The versions build-depends take are checked. A package from Hackage gets a version that the packages that need it take: the package list's when they take it, or else Hackage's newest one they do (miso lock says which). A version that a package does not take is an error before anything is built, and says which package needs what: a version: of miso.yml, one in miso.lock, one of the tool's packages (containers, mtl, transformers), or miso (the release's, or another of miso.yml). When a package read later needs an older version of one chosen before it, miso lock resolves again, with the ranges it found choosing the versions from the start (a few times at most). It does not search other versions to make them fit, as cabal's solver would; the packages built into MicroHs are not checked, as their versions are MicroHs's.
  • miso lock locks what is not in miso.lock yet (a dependency added to miso.yml, one whose git/rev changed, or a package a new one needs), keeps the packages that still match at their versions, and drops those no longer needed.
  • miso update gets mcabal a new package list (the newest Stackage snapshot), locks every package again at its version, and git dependencies at the current commit of their rev, and installs them.
  • A git dependency needs a rev: (a tag, or a commit; a branch works but moves). When the rev is a version tag (v2.1.3 or 2.1.3) that is not the version in the package's description, miso lock and miso update warn.
  • miso.lock is resolved on one OS: a package that only another OS needs (if os(windows), say) is not in it.

miso build and miso install then install exactly that, each package after the ones it needs: the Hackage tarball of each version (checked against its sha256), and the git commit, until the next miso lock or miso update; mcabal installs nothing a locked package needs by itself. Local (path:) dependencies are not locked (they are installed from the directory when its files change), and neither are the packages that come with the tool, unless the project replaces one (see Packages). A dependency that is not in miso.lock (added to miso.yml since, or with a different git/rev) is locked for the install, as without a miso.lock, with a warning.

vendor/

miso vendor runs miso lock, then copies the source of each package in miso.lock to vendor/NAME: the Hackage tarball (checked against its sha256), unpacked, or the files of the git commit (git archive, so no .git). vendor/.miso-vendor lists them, each with its version or commit and a sha256 of its files:

# vendor/.miso-vendor: written by miso vendor.  Commit it; do not edit it.
hackage split 0.2.5.1 18adb60f... files:9f0560fc...

Commit vendor/ with miso.lock. A project with vendor/.miso-vendor is vendored:

  • miso build and miso install install the locked dependencies from vendor/ (or copy them from the store, when it has the same package built), and never download them. A dependency that is not locked, not in vendor/, at another version than miso.lock's, or whose files have changed since miso vendor copied them, is an error: run miso vendor (after miso update, too).
  • miso vendor keeps a package that is there as it copied it, copies again one that changed, and removes one that is no longer a dependency. Other files in vendor/ are left alone. vendor/.gitattributes keeps git from changing line endings (which would change the sha256), and vendor/.gitignore ignores what mcabal builds there.
  • Do not edit the vendored sources; for a patched copy, use a path: dependency.
  • Every package of miso.lock is vendored: the dependencies of miso.yml and the packages they need. Local dependencies and the packages that come with the tool are not. Removing vendor/ makes the project an ordinary one again.

Requirements

On Windows miso runs in MSYS2 (an MSYS2 shell: pacman -S gcc make tar curl git, then the install command above).

  • A C compiler (cc): mhs compiles programs to C.
  • For the browser targets (browser, browser_js), emscripten (emcc).
  • curl, tar, sha256sum or shasum (to check downloads), and git (for git dependencies).
  • hlint, for miso lint only.

miso doctor checks them.

How it is built

src/Miso/CLI/Main.hs imports MicroHs.Main and MicroCabal.Main, so the whole tool is one Haskell program. mhs compiles it to one C file, which is linked with the MicroHs runtime: statically on Linux (musl); on macOS only libSystem is dynamic, as for every macOS program. miso mcabal runs MicroCabal with MHS set to miso mhs, so packages are compiled by the same compiler.

The release tarball is laid out as an mhs installation:

miso-aarch64-darwin/
  bin/miso, bin/cpphs (for modules with CPP)
  mhs-0.16.12.0/       the package database: base, containers, mtl, ..., and the runtime sources
    miso-default         the miso of the release: its repository, commit, version and flags
    miso-prebuilt/       that miso, built (installed in a project that locks it)
    miso-sources/        the source of its libraries and of miso, to build them again

MicroHs and the package database with miso come from the miso flake (pkgs.microhs, pkgs.miso-mhs), MicroCabal from its repository. MicroCabal's Text.ParserComb clashes with MicroHs's, so scripts/prepare-microcabal.sh renames it (and switches its downloads from wget to curl).

nix build .#default        # result/bin/miso
nix build .#tarball        # result/miso-SYSTEM.tar.gz, as released

# or with an installed mhs and the two source trees
make MHSSRC=../MicroHs MCABALSRC=../MicroCabal

Pushing a v* tag builds five platforms (x86_64 and aarch64 Linux and aarch64 macOS with nix; Intel macOS and Windows, which the miso flake's nixpkgs does not support, with scripts/build-release.sh) and attaches them to the release, where install.sh finds them. https://haskell-miso.org/download serves a copy of install.sh: the website's static/download, in haskell-miso.org. A change to install.sh here goes there with a pull request to that repository. miso upgrade downloads install.sh from https://raw.githubusercontent.com/haskell-miso/cli/main/install.sh (or MISO_INSTALL_URL). The release assets must be downloadable without a GitHub login, so the releases need a public repository.

About

πŸ”‹ miso command line tool: build, install, scaffold, hot reload, etc.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages