CLI utility for Torii, mainly used for automation of building/releasing projects.
Torii is a framework for Unity that fills some gaps in its functionality that I've found over the years.
$ pip install toriicli
- Python 3.7+
- Unity, installed via Unity Hub
- NuGet and MSBuild (if you want to use the NuGet subcommand)
toriicli
has commands for the following:
build
: Build a Torii projectfind
: Find an installation of Unitynew
: Create a new Torii projectrelease
: Release a Torii projectnuget
: Manage NuGet packages for project
-p, --project-path
: The path to thetoriiproject.yml
file of the project. If you don't specify this it defaults to the current working directory.
A toriicli
project is a directory with a file called toriiproject.yml
.
The toriiproject.yml
file is a file containing the configuration for that
project.
Please see ./toriicli/example_config.yml
for information on how to configure
toriicli
.
Running toriicli new
in a directory will create a new blank project using
the example config file.
Steps are single transformative operations that take place on a directory of files. Examples of steps include 'import', 'export', 'compress', and 'chmod'.
The directory of files that the step performs operations on is called its
'workspace'. When run, each step has its own isolated workspace directory
as temporary folders created in the OS recommended area for temporary files.
Files can be copied from step to step using the keep
field of a step. It
defaults to **
, so will keep every file from the previous step if not
specified.
A step looks a lot like this:
step: export
keep: "game-{{ build_number }}.zip"
filter:
options: [a_tag]
targets: [StandaloneWindows]
using:
backend: local
container: C:/builds
path_prefix: "{{ build_def.target }}"
Each step has to have the following fields:
step
: The type of step to use.keep
(optional): A glob pattern of which files to keep from the previous step.filter
(optional): Lets you filter on certain factors.using
: What config values to provide to the step. Allows for customisable behaviour.
Some values given to a step are templated. This is useful as sometimes you want
build artifacts to be named based on the target or version number of the build.
In the example above, the build number is used in the keep
field by templating
it like this: game-{{ build_number }}.zip
. When rendered, this template would
simply insert the build number there, like: game-0.1.1234.56789.zip
. Please
note that templated strings need to be wrapped in quotes due to the YAML format.
Additionally, environment variables are respected in fields that are able to
be templated. So you could insert the environment variable $SOME_VARIABLE
by simply specifying it like that as the value in the step config.
Filters can be applied to only run steps under certain conditions. The following filters are available:
targets
: Only run this step for this set of build targets.options
: Only run this step if this option is specified via the CLI (as-o, --option
).
Imports files from an external directory to the step workspace. Supports local store and cloud storage (at the moment just S3).
step: import
using:
backend: s3
region: fra1
endpoint: https://fra1.digitaloceanspaces.com/
container: $BUCKET_NAME
key: "{{ build_def.target }}/game-{{ build_number }}.zip"
Like import, but instead copies files from the step workspace to an external directory.
step: export
keep: "game-{{ build_number }}.zip"
filter:
options: [upload]
using:
backend: s3
region: fra1
endpoint: https://fra1.digitaloceanspaces.com/
container: $BUCKET_NAME
path_prefix: "{{ build_def.target }}"
Compress the workspace into an archive file.
step: compress
using:
format: zip
archive_name: "game-{{ build_number }}"
This would produce an archive called game-0.1.123.56789.zip
. This file
could then be used in the next step by adding this to the step:
keep: "game-{{ build_number }}.zip"
The workspace for that step would then only contain the compressed archive from the prior step.
Change permission bits of a file in the workspace.
step: chmod
using:
file_name: {{ build_def.executable_name }}
permissions: [S_IEXEC]
This step would ensure the executable from the build had the executable flag
set in the filesystem. Valid values for permissions
are listed here.
Run itch.io Butler push on a workspace. Requires Butler to be installed. This is mainly used for released as opposed to builds.
step: butler
filter:
targets: [StandaloneWindows]
using:
directory: "coolgame-{{ build_number }}.zip"
user: my-itchio-user
game: coolgame
channel: windows
user_version: "{{ build_number }}"
This example would push StandaloneWindows
target build archives to the
windows
channel of the itch.io project.
$ toriicli build
Builds are defined as build_defs
in the project config file. A build
definition has 2 things:
target
: The platform this build is for, from: https://docs.unity3d.com/ScriptReference/BuildTarget.htmlexecutable_name
: The name of the executable. I.e."LSDR.exe"
for"StandaloneWindows"
, or"LSDR.app"
for"StandaloneOSX"
.
When toriicli build
is run in the project directory, the build defs are used
to generate a JSON file that's placed in the Unity project directory, and
Unity is executed with the unity_build_execute_method
defined in the project
config. This is a static C# method that runs when Unity is launched. It
loads the generated build defs file, and builds the game for each platform in
a folder (configured by build_output_folder
).
When Unity exits successfully, toriicli
attempts to collect information from
the builds based on the build defs it was given. It gets the path to each build
as well as a version number from its assembly.
The build info for each build is then submitted to the build post-steps, where files from the build are transformed in a number of steps before reaching their intended destination (be it a folder, or some cloud storage somewhere).
Once the post steps have run, the builds are removed from the build output
folder, and the generated build defs JSON file is also deleted. This behaviour
can be toggled by providing the --no-clean
option to build
.
Build post-steps are specified in the build_post_steps
area of the project
config file. They are run for every build def specified after the Unity build
completes.
Build post-steps have an implicit (unspecified) import
step as the first
step. This step imports from the build output directory into a step workspace.
It's the exact same as specifying this as the first step (don't do this!):
step: import
using:
backend: local
container: "{{ path }}"
build_post_steps:
- step: compress
using:
format: zip
archive_name: "game-{{ build_number }}"
- step: export
keep: "game-{{ build_number }}.zip"
using:
backend: local
container: C:/game-builds
path_prefix: "{{ build_def.target }}"
- step: export
keep: "game-{{ build_number }}.zip"
filter:
options: [upload]
using:
backend: s3
region: fra1
endpoint: https://fra1.digitaloceanspaces.com/
container: $BUCKET_NAME
path_prefix: "{{ build_def.target }}"
This example will compress the build files into a zip, copy the zip to
C:/game-builds/{target-name}/game-{build_number}.zip
, and upload
to S3 cloud storage if the build command is run with the upload option (like
this: toriicli build --option upload
).
$ toriicli release 0.1.123.4567
So you've built the game and uploaded it somewhere - what happens when you
want to release it? Well you can use the release
command for that.
You run it with a build number and it will run steps specified in the
release_steps
section of the project config.
The release
command supports -o, --option
for step filtering in the same
way as build post-steps.
release_steps:
- step: import
using:
backend: s3
region: fra1
endpoint: https://fra1.digitaloceanspaces.com/
container: $BUCKET_NAME
key: "{{ build_def.target }}/game-{{ build_number }}.zip"
- step: butler
filter:
targets: [StandaloneWindows]
using:
directory: "game-{{ build_number }}.zip"
user: my-itchio-user
game: gamename
channel: windows
user_version: "{{ build_number }}"
Say you had just built and uploaded to cloud storage version 0.1.123.4567
of
your game (zipped). If you ran toriicli release 0.1.123.4567
with the steps
above, it would download that version of the archive from cloud storage
(see the key
field of the import step), and then run butler using the zip
to release it. Pretty nifty.
Toriicli has the nuget
subcommand for working with project NuGet packages.
To use this subcommand, you need the NuGet CLI
and a version of MSBuild. MSBuild comes with Visual Studio, but you can use
JetBrains MSBuild
or Mono MSBuild if you don't want to install Visual Studio.
Note: in order to function correctly, NuGet must be configured to install packages
into the nuget-packages
directory in your Unity project folder. If you created
your Torii project with toriicli new
then you don't need to worry about this.
If you are adapting an existing project to use toriicli
, then make a file called
nuget.config
in your Unity project folder, and put this text in it:
<configuration>
<config>
<add key="repositoryPath" value="nuget-packages" />
</config>
</configuration>
This will ensure all NuGet packages get installed to the nuget-packages
folder.
The reason for this is that Unity installs its own packages to the packages
folder,
which NuGet is configured by default to use. They cannot both share the same space,
so we need to install NuGet packages to a separate place, as Unity is a bad roommate.
You'll also need a blank packages.config
file, like this:
<?xml version="1.0" encoding="utf-8"?>
<packages>
</packages>
Also note: it's best to run these NuGet commands when Unity is not running, as sometimes it will access the installed packages causing permissions errors.
To install a package, simply run:
$ toriicli nuget install <package>
You can optionally specify a version too:
$ toriicli nuget install <package> --version 0.1.2
This will use NuGet to install the package to the nuget-packages
folder, it will
add the package to your packages.config
file, and it will copy the installed
package into your Unity project. By default it will copy it into the
Assets/NuGetPackages
folder, but this can be configured by setting
nuget_package_install_path
in the config.
Additionally, by default it will install the package for the latest version of
the .NET Framework it can, with a maximum version of .NET Framework 4.6.2.
This is because as far as I could see, this is the latest possible version
Unity supports. If you'd like to customise this, you can configure this by
setting unity_dotnet_framework_version
in the config.
To uninstall a package, run:
$ toriicli nuget uninstall <package>
This will remove it from your Unity project, and your packages.config
.
To sync your installed packages with your packages.config
file, run:
$ toriicli nuget restore
This will reinstall all packages listed in packages.config
. This is useful
when cloning a repo from fresh, as packages won't normally be committed to
the remote.