MixLab 0.0.20

PHP, Node, Python, Ruby, Go and Java versions

This handbook covers MixLab through the mix command line. If you would rather work in a graphical interface, you already have one: every installer places the MixLab window beside the command line. Both drive the same MixEngine underneath, so everything in this handbook still applies.

MixLab installs language runtimes into its own directory, one immutable folder per version, and never touches whatever your operating system already has. Installing a version never modifies a version already installed, so nothing you have working can be broken by adding something new.

Six languages are managed: PHP, Node.js, Python, Ruby, Go and Java, plus one tool, Composer, which installs the same way and runs under whichever PHP the directory uses.

Installing a version

mix runtime available --kind php
mix runtime install php 8.3.33
mix runtime list

The version is exact, on purpose. 8.3 asks "choose one for me", and there is nothing to choose from until something is installed. Choosing between versions is what resolution does, and resolution answers with what is on the machine. mix runtime available is where a range belongs.

An install is a job, and mix waits for it by default: mix runtime install php 8.3.33 && … is a sentence about PHP being there. --no-wait returns as soon as the daemon has accepted the work and hands you a job id, which mix job wait can be pointed at later.

Installing a PHP also creates its php-fpm pool, such as php-fpm@8.3.33: a service like any other, in mix service list. Node, Python, Ruby, Go and Java are invoked per command and have nothing supervised.

On a Windows PC with an ARM processor

Some versions have no build made for that processor. Nobody publishes an ARM64 Windows PHP, for instance. Where that is so, MixLab installs the x86_64 build instead and Windows runs it for you. It works; it is a little slower than a build made for your machine would be.

You are never left to guess which is which. mix runtime available and mix package available grow a RUNS column on that machine, saying native or emulated per version, and the install says so before it starts downloading. On every other machine the column is not there, because there is nothing for it to say.

Updating to a newer patch

mix runtime available lists one row per line, such as PHP 8.4 or Node.js 22, showing the newest release of each and how many others it has. --all lists every release, and --kind php --line 8.4 lists the releases of one line. When a version you have installed has a newer release in its line, the listing says so above the table:

update: php 8.4.24 → 8.4.25   mix runtime upgrade php 8.4.24
mix runtime upgrade php 8.4.24 --dry-run   # print what would move, and change nothing
mix runtime upgrade php 8.4.24             # print the same plan, then ask

The update installs 8.4.25, turns on the same PHP extensions, gives its php-fpm pool the settings the old pool had, and moves your sites, the default and every project pinned to exactly 8.4.24 over to it. Then 8.4.24 is removed. Add --keep to keep it.

The old version also stays when something still needs it: a mixengine.toml that pins exactly that patch, a tool you installed into it with npm install -g or pip install, or an extension such as phpMyAdmin that only the old version satisfies. The plan names each one. MixLab never edits your mixengine.toml, and it does not install your global tools again under the new version.

An update stays inside its line. Going from PHP 8.3 to 8.4 changes which extensions exist, so mix runtime upgrade refuses it: install 8.4 and change the pin yourself.

In the MixLab window, an installed version with a newer patch has an Update to 8.4.25 row under it. The dialog shows the same plan and a box to keep the old version, and the row shows the progress while the update runs.

Composer

mix runtime available --kind composer   # the versions the index offers
mix runtime install composer 2.10.3     # exact, like every install
composer --version                      # runs composer.phar under this directory's PHP
mix project update shop --pin composer=2.2

Composer is a file, not a program: the composer command starts the PHP your directory resolves to and hands it composer.phar. So MIXENGINE_PHP=8.1 composer install uses PHP 8.1, and a directory pinned to PHP 7.4 needs the 2.2 line, because Composer 2.3 and later want PHP 7.2.5 or newer.

Your PHPPin
7.2.5 and newercomposer = "2"
5.3 – 7.2.4composer = "2.2"

Installing Composer creates no service and runs nothing; mix runtime list shows it beside the languages.

Go

mix runtime available --kind go        # 1.21 to the newest release
mix runtime install go 1.25.14
go version                             # the Go this directory resolves to
mix project update api --pin go=1.25

go and gofmt are commands like every other. GOROOT is worked out by go itself from where it is installed, and GOPATH, the module cache and the build cache stay where Go puts them. Every version shares them, which is how Go is designed to be used.

A pinned Go is the Go that builds. A go.mod asking for a newer release than the one your directory resolves to does not quietly download that release and run it instead: a go started through MixLab runs with GOTOOLCHAIN=local, so such a module stops with Go's own message.

go: go.mod requires go >= 1.27 (running go 1.25.14; GOTOOLCHAIN=local)

The answer is to install the newer Go and pin it. Three details:

mix doctor tells you when MixEngine itself was started with a GOTOOLCHAIN other than local, or with a GOROOT, because the commands it starts inherit them.

Programs you add with go install land in Go's own GOBIN (~/go/bin unless you changed it), which MixLab does not put on your PATH.

Java

mix runtime available --kind java      # the long-term-support lines: 11, 17, 21 and 25
mix runtime install java 21
java --version                         # the JDK this directory resolves to
mix project update api --pin java=21

java, javac, jar, jshell, keytool and jlink are commands like every other. Each is started with JAVA_HOME set to the JDK it belongs to, even if you have exported another one, so a program, and any build it runs itself, finds the JDK this directory asked for.

Maven and Gradle typed in a terminal read your own JAVA_HOME first. If yours points at a system JDK, mvn and ./gradlew use that one whatever the directory pins; unset it and they find the pinned java on your PATH instead. mix doctor tells you when MixEngine itself was started with a JAVA_HOME outside its own JDKs.

HTTPS to your own sites works. MixLab writes its certificate authority into each installed JDK's certificate store, so https://blog.test verifies from Java with no flag and no extra argument. Two limits are worth knowing: a runtime you build yourself with jlink carries the original store and does not trust these sites, and a JVM started with -Djavax.net.ssl.trustStore reads that store instead of the JDK's own. If a JDK has lost it, mix doctor --repair puts it back.

On Linux a JDK expects some of the system's libraries: zlib to start at all, freetype for fonts, X11 for windows and ALSA for sound. When your system does not have one, the install says which and carries on: a server that never draws a window or plays a sound runs without them, and your distribution's package manager has them when you need them.

Choosing which one a directory uses

Nothing here changes a shell, patches a profile, or asks you to type an activate command. A directory resolves to a version, and the shims do the rest.

mix runtime default php 8.3.33      # the machine-wide fallback
mix project update blog --pin php=^8.1
mix runtime resolve php             # what does *this* directory get, and why?

mix runtime resolve is the command to remember. It answers what php -v would answer, without running anything, and it names which of four sources decided it:

  1. An explicit flag or environment variable on the command being run.
  2. The nearest mixengine.toml that names this language, walking up from where you are.
  3. The registered project covering this directory.
  4. The global default.

A mixengine.toml that says nothing about PHP is not an answer about PHP, so a pin further up still applies.

Writing a constraint

Pins and --version accept three shapes, all resolved against installed versions and never silently against downloadable ones:

WrittenMeans
8.3.33Exactly that
8.3 or 8As many segments as are written have to agree; one nobody wrote is a zero
^8.3Up to the leftmost non-zero segment: ^0.12 stops before 0.13

A constraint with no pre-release in it never selects one. 8.5 and ^8.5 both pass over 8.5.0RC1; naming it exactly is how you ask for it.

The shims

mix path install puts <root>/bin on your PATH. It holds a small program per command of each language you have installed with MixEngine (php, composer, node, npm, python, pip, ruby, go, java and the rest), and each one works out which version this directory wants and hands over to the real binary.

Three things follow that are worth knowing:

Only <root>/bin goes on your PATH: one entry, never a directory per version.

mix path status
mix path uninstall

mix path uninstall takes the directory back off your PATH and leaves the commands where they are: they live inside MixEngine's own home, and removing the home is what removes them.

PHP extensions

Extensions are per installed version, because that is what they are compiled against:

mix runtime ext list --php 8.3.33
mix runtime ext enable redis --php 8.3.33
mix runtime ext disable xdebug --php 8.3.33

list says which extensions the build has and why each one is on or off, which is usually the question. Leaving --php out means the version this directory resolves to.

Enabling loads the extension on every PHP process of that version, the pool included.

Removing a version

mix runtime uninstall php 8.1.31

This is refused while a registered project pins that version, naming the projects, and while the php-fpm pool running out of it is running. --force crosses the first of those and never the second.


This page as Markdown: https://mixnz.github.io/mixlab/en/runtimes.md