The Basics

Installing a package

  • If you know the name
    paxy install {name} installs a given package or packages by name.
  • If you don't know the name
    paxy search {query} searches for a package by name or description.

Note: add works as an alias for install.

Updating a package

paxy update {name} updates a given package or packages by name Alternatively, paxy by itself updates the entire system.

Note: upgrade works as an alias for update.

Removing a package

paxy remove {name} removes a given package or packages by name.

Note: uninstall works as an alias for remove.

Listing packages

paxy list lists all installed packages.

Automatic correction

For all commands, paxy will automatically correct typos and abbreviations. For example, imagine a scenario where bash isn't installed, but sh is. paxy remove bash will provide you with a prompt: "Did you mean 'sh'? [Y/n]". If you answer "Y", paxy will remove sh instead.

Warning: please don't remove sh. That is a horrible idea.

The Basics

Installing a package

  • If you know the name
    paxy install {name} installs a given package or packages by name.
  • If you don't know the name
    paxy search {query} searches for a package by name or description.

Note: add works as an alias for install.

Updating a package

paxy update {name} updates a given package or packages by name Alternatively, paxy by itself updates the entire system.

Note: upgrade works as an alias for update.

Removing a package

paxy remove {name} removes a given package or packages by name.

Note: uninstall works as an alias for remove.

Listing packages

paxy list lists all installed packages.

Automatic correction

For all commands, paxy will automatically correct typos and abbreviations. For example, imagine a scenario where bash isn't installed, but sh is. paxy remove bash will provide you with a prompt: "Did you mean 'sh'? [Y/n]". If you answer "Y", paxy will remove sh instead.

Warning: please don't remove sh. That is a horrible idea.

Environments

What is an environment?

A Paxy environment is like a docker container. It allows you to use a specific version of a package without installing it globally. This is useful for testing, or if you want to use a different version of a package than the one installed globally. It's also useful if the package is temporary. For example, if you need a specific version of python, but you don't usually use python, you can create an environment for that version of python, and when you're done it is automatically removed.

How do I create an environment?

Environments are created with paxy env {package(s)}. For example, to create an environment with python 3.6, you would run paxy env python@3.6. You will be provided with an instance of your system shell, or the shell specified in your configuration. You can then use the package as you would normally. For example, to install a python package, you would run pip install {package}. You can specify more than one package to the command.

How are environment packages stored?

They are stored on the system as regular packages, with the env flag set. A paxy clean command will remove them permanently.

Creating a Package

Before You Begin

Consider the following items:

  • Does this package already exist?
    • If so, are you uploading a specific version? Add a version to the original package instead.
  • Does this package contain everything necessary for it to work?

What should a package have?

A package should contain the following:

  • The package itself
  • Any pieces of the package that are necessary for use.

For example, consider the rust package. It downloads rustup, then uses rustup to install rustc and cargo. This provides a complete rust environment.

Package Versions

Package versions should correspond to versions of the actual software. For example, version 3.6 of python should install python 3.6. Package maintainers should make an effort to provide all versions of a package (automatic deployment of non-breaking versions is recommended).

Package Flavors

Package flavors are often provided in the form of a specific architecture. For example, rust uses flavors for the target triple. A package should provide flavors for anything that seems relevant. In other cases, package flavors specify the origin of the package. For example, jdk provides flavors openjdk and oracle.

Package Names

A package name should reflect what's inside the package. If the package provides a single executable, it should be named for that executable. Otherwise, if a package provides a collection of software, like gcc, it should be named after the cannonical name for that software. In addition, if there are multiple sources for said software, like Java JDKs, they should be specified in the package flavors.

Begin

Now that you have considered all of these, you may continue on to the next section to begin your package.

Creating a Package

Before You Begin

Consider the following items:

  • Does this package already exist?
    • If so, are you uploading a specific version? Add a version to the original package instead.
  • Does this package contain everything necessary for it to work?

What should a package have?

A package should contain the following:

  • The package itself
  • Any pieces of the package that are necessary for use.

For example, consider the rust package. It downloads rustup, then uses rustup to install rustc and cargo. This provides a complete rust environment.

Package Versions

Package versions should correspond to versions of the actual software. For example, version 3.6 of python should install python 3.6. Package maintainers should make an effort to provide all versions of a package (automatic deployment of non-breaking versions is recommended).

Package Flavors

Package flavors are often provided in the form of a specific architecture. For example, rust uses flavors for the target triple. A package should provide flavors for anything that seems relevant. In other cases, package flavors specify the origin of the package. For example, jdk provides flavors openjdk and oracle.

Package Names

A package name should reflect what's inside the package. If the package provides a single executable, it should be named for that executable. Otherwise, if a package provides a collection of software, like gcc, it should be named after the cannonical name for that software. In addition, if there are multiple sources for said software, like Java JDKs, they should be specified in the package flavors.

Begin

Now that you have considered all of these, you may continue on to the next section to begin your package.

Package Manifest

The package manifest is split into three parts. First, we have the base package. A package contains multiple flavors, each flavor contains multiple versions.

Package metadata is specified in essentially any file format, however, examples in this file will be in toml. Many other formats are supported, and translations to those file formats can be found in Manifest Formats. Structure goes as follows:

[package]
name = "package-name"
description = "A package description"
authors = ["Author Name <Author Email>"]
license = "License"
homepage = "https://example.com"
repository = "https://example.com"
documentation = "https://example.com"
readme = "README.md"
keywords = ["keyword1", "keyword2"]

[dependencies]
dependency1 = "1.0.0"
dependency2 = {
    version = "1.0.0",
    flavor = "flavor"
}

[flavors]
flavor = "path/to/flavor.toml"

package

The package table contains metadata about the package itself. The following fields are supported (* = required):

  • name*: The name of the package. This is the name that will be used to refer to the package in other packages' manifests, as well as by users during installation.
  • description*: A description of the package. This should be a short description of what the package does.
  • authors: A list of authors of the package. This should be a list of git identifiers (Name (Domain) <Email>).
  • license: The license of the package. This should be a valid SPDX license identifier.
  • homepage: The homepage of the package. This should be a URL to the package's homepage.
  • repository: The repository of the package. This should be a URL to the package's repository.
  • documentation: The documentation of the package. This should be a URL to the package's documentation.
  • readme: The readme of the package. This should be a path to the package's readme from the directory the manifest is in.
  • keywords: A list of keywords for the package. These keywords should describe the package.

dependencies

The dependencies table lists all dependencies that are required for all flavors of the package. Each entry should point to either a version or a table with version and flavor fields.

flavors

The flavors table lists all flavors of the package. Each entry contain a path to a flavor manifest.

Flavor Manifests

Each flavor specifies its own metadata, dependencies, and all versions of itself.

[flavor]
description = "A flavor description"
authors = ["Author Name <Author Email>"]
license = "License"
homepage = "https://example.com"
repository = "https://example.com"
documentation = "https://example.com"
readme = "README.md"

[versions]
"0.1.0" = "path/to/0.1.0.toml"

flavor

The flavor table contains metadata about the flavor itself. The following fields are supported (* = required):

  • description*: A description of the flavor. This should explain the differences between this flavor and other flavors of the same package.
  • license, homepage, repository, documentation, readme: The same as in the package table. Only include if they're different from the package's.

versions

The versions table lists all versions of the flavor. Each entry should point to a version manifest.

Version Manifests

You're almost at the end. You've created a package, and you've created a flavor. Now you need to create a version. This is the last step in creating a package. A version manifest contains all the information necessary to install a specific version of a package.

[version]
authors = ["Author Name <Author Email>"]
license = "License"
homepage = "https://example.com"
repository = "https://example.com"
documentation = "https://example.com"
readme = "README.md"

[step1]
type = "clone"
url = "https://example.com"
branch = "master"
commit = "1234567890abcdef"

[step2]
type = "copy"
source = "path/to/source"
destination = "path/to/destination"

[step3]
type = "run"
command = "command"

[install]
steps = ["step1", "step2", "step3"]
artifacts = ["path/to/artifact1", "path/to/artifact2"]

[dependencies]
dependency1 = "1.0.0"
dependency2 = {
    version = "1.0.0",
    flavor = "flavor"
}

version

All of the fields under version need only be specified if they different from the prior manifests.

dependencies

The dependencies table lists all dependencies that are required for this version of the flavor. Each entry should point to either a version or a table with version and flavor fields.

install

steps

Takes a list of steps. Each step is a key in the manifest. The steps are executed in the order they are listed. The following step types are supported (* = required):

  • clone: Clones a repository. The following fields are supported:
    • url*: The URL of the repository to clone.
    • branch: The branch to clone. Defaults to master.
    • commit: The commit to clone. Defaults to the latest commit on the branch.
  • copy: Copies a file or directory. The following fields are supported:
    • source*: The source of the file or directory to copy.
    • destination*: The destination of the file or directory to copy.
  • run: Runs a shell command. The following fields are supported:
    • command*: The command to run.

All install steps are run in an isolated environment without root access, and artifacts should be installed to the local directory.

artifacts

Artifacts is a list of paths to files or directories that should be installed. These paths are relative to the local directory. All listed artifacts will be symbolically linked to the target directory.

Manifest Formats

Package manifests can be written in nearly any language. The following are all directly supported, but more could be implemented with little difficulty. The only requirement is a rust serde implementation.

toml

The default format for manifests is toml. This is the format used in the examples in this book.

json

Package Manifest

{
    "package": {
        "name": "package-name",
        "description": "A package description",
        "authors": ["Author Name <Author Email>"],
        "license": "License",
        "homepage": "https://example.com",
        "repository": "https://example.com",
        "documentation": "https://example.com",
        "readme": "README.md",
        "keywords": ["keyword1", "keyword2"]
    },
    "dependencies": {
        "dependency1": "1.0.0",
        "dependency2": {
            "version": "1.0.0",
            "flavor": "flavor"
        }
    },
    "flavors": {
        "flavor": "path/to/flavor.json"
    }
}

Flavor Manifest

{
    "flavor": {
        "description": "A flavor description",
        "authors": ["Author Name <Author Email>"],
        "license": "License",
        "homepage": "https://example.com",
        "repository": "https://example.com",
        "documentation": "https://example.com",
        "readme": "README.md"
    },
    "versions": {
        "0.1.0": "path/to/0.1.0.json"
    }
}

Version Manifest

{
    "version": {
        "authors": ["Author Name <Author Email>"],
        "license": "License",
        "homepage": "https://example.com",
        "repository": "https://example.com",
        "documentation": "https://example.com",
        "readme": "README.md"
    },
    "step1": {
        "type": "clone",
        "url": "https://example.com",
        "branch": "master",
        "commit": "1234567890abcdef"
    },
    "step2": {
        "type": "copy",
        "source": "path/to/source",
        "destination": "path/to/destination"
    },
    "step3": {
        "type": "run",
        "command": "command"
    },
    "install": {
        "steps": ["step1", "step2", "step3"],
        "artifacts": ["path/to/artifact1", "path/to/artifact2"]
    }
    "dependencies": {
        "dependency1": "1.0.0",
        "dependency2": {
            "version": "1.0.0",
            "flavor": "flavor"
        }
    }
}

yaml

Package Manifest

package:
    name: package-name
    description: A package description
    authors:
        - Author Name <Author Email>
    license: License
    homepage: https://example.com
    repository: https://example.com
    documentation: https://example.com
    readme: README.md
    keywords:
        - keyword1
        - keyword2
dependencies:
    dependency1: 1.0.0
    dependency2:
        version: 1.0.0
        flavor: flavor
flavors:
    flavor: path/to/flavor.yaml

Flavor Manifest

flavor:
    description: A flavor description
    authors:
        - Author Name <Author Email>
    license: License
    homepage: https://example.com
    repository: https://example.com
    documentation: https://example.com
    readme: README.md
versions:
    "0.1.0": path/to/0.1.0.yaml

Version Manifest

version:
    authors:
        - Author Name <Author Email>
    license: License
    homepage: https://example.com
    repository: https://example.com
    documentation: https://example.com
    readme: README.md
step1:
    type: clone
    url: https://example.com
    branch: master
    commit: 1234567890abcdef
step2:
    type: copy
    source: path/to/source
    destination: path/to/destination
step3:
    type: run
    command: command
install:
    steps:
        - step1
        - step2
        - step3
    artifacts:
        - path/to/artifact1
        - path/to/artifact2
dependencies:
    dependency1: 1.0.0
    dependency2:
        version: 1.0.0
        flavor: flavor