Distributing modules: the configuration and structure
Preparing the module
For a module to work in any of the ecosystems, it needs to follow a certain structure.
We strongly suggest that you use some of the Module builder and authoring tools that are available.
Quick Overview using fez
To create a skeleton, run the commands:
fez init MyNew::Module
# Will create the following:
# MyNew--Module/
# āāā lib
# āĀ Ā āāā MyNew
# āĀ Ā āāā Module.rakumod
# āāā META6.json
# āāā t
# āāā 00-use.rakutest
If you need to add new modules, classes, resources, build-depends, or depends you may use the following (respectively, these resources will automatically be added to META6.json):
fez module My::New::Module
fez module --class My::New::Module
fez resource xyz
fez depends --build Build::Dependency
fez depends Runtime::Dependency
Full Explanation
Even if you're intending to use fez or an equivalent (and you should), the following can be useful reading before releasing a module, so that, as you develop your module, you will be aware of the requirements and customs regarding how a module is structured.
The META6.json file specifies the various metadata of your project and
their uses; this has various sections that align with the filesystem of
your module, so the following sections will step through the filesystem
and the META6.json file in parallel; some sections belong only to one
or the other, but many belong to both. The filesystem items will be
referred to as file or directory, and the META6.json sections
will be referred to as a key or a section.
The module root directory and META6.json file
The attributes in this file are analyzed by the META6 class. They are divided into optional, mandatory and customary. Mandatory are the ones you need to insert into your file, and customary are those used by the current Raku ecosystem and possibly displayed on the module page if it's published, but you have no obligation to use it.
Create a project directory named after your module. For
example, if your module is Vortex::TotalPerspective, then create a
project directory named Vortex-TotalPerspective.
Make your project directory look like this:
Vortex-TotalPerspective/
āāā META6.json
āāā LICENSE
āāā README.md
āāā lib
ā āāā Vortex
ā āāā TotalPerspective.rakumod
āāā bin
ā āāā vortex
āāā t
āāā basic.rakutest
Make your META6.json|Reference,META6.json file look something like this:
{
"name" : "Vortex::TotalPerspective",
"description" : "Wonderful simulation to get some perspective.",
"source-url" : "git://github.com/you/Vortex-TotalPerspective.git"
"auth" : "github:SomeAuthor",
"authors" : [ "Your Name" ],
"tags": [
"Vortex", "Total", "Perspective"
],
"depends" : [ ],
"build-depends" : [ ],
"test-depends" : [ ],
"license" : "Artistic-2.0",
"version" : "0.0.1",
"api" : "1",
"raku" : "6.c",
"provides" : {
"Vortex::TotalPerspective" : "lib/Vortex/TotalPerspective.rakumod"
},
"resources" : [ ],
}
There are more fields described in the
META design documents,
but not all of these are
implemented by existing package managers. Hence you should stick to the fields
described in the above example block to ensure compatibility with existing
package managers such as zef. You can also check
Moritz Lenz's repository of all modules for examples,
but bear in mind that some of them might use fields, such as source-type
above, which are currently ignored.
The name key
The name key is compulsory, and zef will fail if you do not include it.
Even if you have created a META6.json file just to express the dependencies
for a series of scripts, this section must be included.
The description key
The description field is also mandatory, and includes a short
description of the module.
The source-url key
source-url indicates the URL of the repository where the module is
developed; this is one of the customary modules if you are going to publish it
in the module ecosystem. The current module ecosystem will link this URL from
the project description. [1]
See also the auth section, below.
The auth and authors sections
The authors section includes a list of all the module authors. In
the case there is only one author, a single element list must be
supplied. This field is optional.
The auth section identifies the author in GitHub or other repository
hosting site, such as Bitbucket or GitLab. This field is customary,
since it's used to identify the author in the ecosystem, and opens the
possibility of having modules with the same name and different authors.
The tags section
The tags section is also optional. It is used to describe
the module in the Raku ecosystem.
The depends, build-depends, and test-depends sections
The depends, build-depends, and test-depends sections include different modules that
are used in those phases of the of installation. All are optional, but
if present must contain the required modules for those
phases. These dependencies might optionally use
Version specification strings; zef will check for the
presence and versions of these modules and install or upgrade them if needed.
//...
"depends": [
"URI",
"File::Temp",
"JSON::Fast",
"Pod::To::BigPage:ver<0.5.0+>",
"Pod::To::HTML:ver<0.6.1+>",
"OO::Monitors",
"File::Find",
"Test::META"
],
//...
Additionally, depends can be either an array as above or a hash that uses two
keys, runtime and build, whose function should be self-descriptive, and
which are used, for instance,
in Inline::Python:
//...
"depends" : {
"build": {
"requires": [
"Distribution::Builder::MakeFromJSON",
{
"from" : "bin",
"name" : {
"by-distro.name" : {
"macosx" : "python2.7-config",
"debian" : "python2.7-config",
"" : "python2-config"
}
}
}
]
},
"runtime": {
"requires": [
"python2.7:from<native>"
]
}
}, // ...
In general, the array form will be more than enough for most cases.
The README.md file
The README.md file is a
markdown-formatted
text file, which will later be automatically rendered as HTML by GitHub/GitLab for modules kept
in those places, or by raku.land website for modules
accessible with zef.
The LICENSE file and key
Regarding the
LICENSEfile, if you have no other preference, you might just use the same one that Rakudo Raku uses. Just copy/paste the raw form of its license into your ownLICENSEfile.The license field in META6.json should be one of the standardized names listed here: https://spdx.org/licenses/. In the case of the Artistic 2.0 license, which is what many of our ecosystem modules use, its identifier is
Artistic-2.0. Having standardized identifiers make it easy for humans and computers alike to know which license was actually used by looking at the metadata!If you can't find your license on
spdx.orgor you use your own license, you should put the license's name in the license field. For more details see https://github.com/Raku/old-design-docs/blob/master/S22-package-format.pod#license.
The versioning keys
The version key
For choosing a version numbering scheme, try and use "major.minor.patch" (see
the spec on versioning for
further details). This will go into the version key of META6.json. This
field is optional, but used by installation to match against installed version,
if one exists.
The api key
Optionally, you can set an api field. Incrementing this indicates
that the interface provided by your module is not backwards compatible
with a previous version. You can use it if you want to adhere to
Semantic Versioning. A best practice is to simply
keep the api field to the same value as your major version number. A
dependency can then depend on your module by including an :api part,
which will ensure backwards incompatible releases will not be pulled in.
The raku key
Set raku version to the minimum Raku version your module works with. This
field is mandatory. Use 6.c if your module is valid for Christmas release and
newer ones, use 6.d if it requires, at least the Diwali version.
The provides section and the lib directory
If your project contains other modules that help the main module do its job, they should go in your lib directory like so:
lib
āāā Vortex
āāā TotalPerspective.rakumod
āāā TotalPerspective
āāā FairyCake.rakumod
āāā Gargravarr.rakumod
In the provides section, include all the namespaces provided by your
distribution and that you wish to be installed; only module files that are
explicitly included here will be installed and available with use or
require in other programs. This field is mandatory.
In the provides object (object in the JSON sense), the key is the module
name, the value is the path from the module root to the file in the lib/
directory.
The bin directory
Programs and scripts that you need in the $PATH for execution need
to be included in the bin directory; these will be copied to whatever
directory your installed Rakudo distribution has allotted for executables;
typically this will be /path/to/installation/share/perl6/site/bin/, a folder
that should be available in $PATH. Note: the perl6 path component predates the
language name change.
The resources directory and section
The resources section is optional, but if present, should contain a list
of the files in your resources directory that you wish to be installed.
These will be installed with hashed names alongside your library files.
If you have any additional files (such as templates or a dynamic
library) that you wish to have installed so you can access them at
runtime, they should be placed in a resources subdirectory of your project, e.g.:
resources
āāā templates
āāā default-template.mustache
The file must then be referenced in META6.json (see below for more on
META6.json) so that the distribution path can be provided to the program.
{
"name" : "Vortex::TotalPerspective",
"provides" : {
"Vortex::TotalPerspective" : "lib/Vortex/TotalPerspective.rakumod"
},
"resources": [ "templates/default-template.mustache"]
}
The additional file can then be accessed inside module code; see the section on the %?RESOURCES variable, below.
The $?DISTRIBUTION variable|Modules,%?DISTRIBUTION
The $?DISTRIBUTION variable
is only populated within files listed in the provides
section (usually those in the lib directory). If you
want to use it outside that (i.e. in your tests), you'll
need to provide a Routine that returns it.
The %?RESOURCES variable|Modules,%?RESOURCES
Note the example here is returning a Distribution::Resource object. It should not be considered as a path to an object in the filesystem.
my $template-text = %?RESOURCES<templates/default-template.mustache>.slurp;
Note: Accessing these files via %?RESOURCES is not
getting their installed locations or an IO class: You are accessing a Distribution::Resource.
object indexed on the name provided. It's important to be careful in
how you use these to avoid embedding the filename at compile-time
which might be different than the location the file is in at runtime.
The $?RESOURCES variable
is only populated within files listed in the provides
section (usually those in the lib directory). If you
want to use it outside that (i.e. in your tests), you'll
need to provide a Routine that returns it.
An example of how to use this might be:
sub MyModule-resources(*@resources) is export(:tests) {
for @resources -> $resource-filename {
my $resource = %?RESOURCES{$resource-filename};
# Just some error-checking code
if $resource !~~ Distribution::Resource {
note "Could not find resource '$resource-filename' in distribution";
say qx{ls -laF ; ls -laF resources ; cat META6.json};
say "Resource is: " ~ $resource.raku;
say "Resource keys are: " ~ %?RESOURCES.keys.raku;
}
}
return %?RESOURCES;
}
...and then to use it in your code:
use TOP :tests :DEFAULT; # Note that the :tests here matches the "is export" above
my $resource-name = 'templates/default-template.mustache';
my $resources = MyModule-resources($resource-name);
my $resource = $resources{$resource-name};
my $file_handle = $resource.open();
The above will produce quite a bit of debugging output if the resource isn't found (but will continue regardless). This can be handy when running in a remote location, such as a GitHub action.
The t directory, and testing your module
If you don't yet have any tests, you can leave out the t
directory and basic.rakutest file for now. For more information on how to write
tests (for now), you might have a look at how other modules use
Test.
The Test::META module can help you check the correctness of the META6.json file; this module will check for all the mandatory fields and that the type used for all of them is correct.
If you want to test your module you can use the following command to install the module directly from the module folder you just created.
zef install ./your-module-folder
Note that doing so precompiles and installs your module. If you make changes to
the source, you'll need to re-install the module.
(See use lib pragma, -I
command line switch, or RAKULIB environment variable, to include a path to your
module source while developing it, so you don't have to install it at all).
See also the test-depends section, above.
Documenting your modules
To document your modules, use Raku Pod markup inside them.
Module documentation is most appreciated and will be especially important once
the Raku module directory (or some other site) begins rendering Pod docs as
HTML for easy browsing. If you have extra docs (in addition to the Pod docs in
your module(s)), create a doc directory for them. Follow the same folder
structure as the lib directory like so:
doc
āāā Vortex
āāā TotalPerspective.rakudoc
Build hooks
If your module requires extra processing during installation to fully
integrate with and use non-Raku operating system resources, you may
need to add a Build.rakumod|Reference,Build.rakumod file (a "build hook") to the top-level directory. It will
be used by the zef installer as the first step in the installation process.
See the README for zef for a brief example. Also see various usage scenarios in existing
ecosystem modules such as zef itself.
Documentation about Modules
Want to distribute your modules?
[1]Some old modules also provide a source-type field,
which was used to indicate the kind of source control system, generally git,
which can be used to download the module. However, this field is nowadays
ignored by zef and the rest of the tools.
[2]Note, described above is a minimal project directory. If your project
contains scripts that you'd like distributed along with your module(s),
put them in a bin directory. If you'd like a graphical logo to
appear next to your module at the module directory, create a
logotype directory and put into it a logo_32x32.png file. At some
point, you might also consider adding CONTRIBUTORS, NEWS,
TODO, or other files.