The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

# ABSTRACT: A narrative introduction to Pinto
#------------------------------------------------------------------------------
# VERSION
#------------------------------------------------------------------------------
1;
__END__
=pod
=encoding UTF-8
=for :stopwords Jeffrey Ryan Thalhammer
=head1 NAME
Pinto::Manual::Tutorial - A narrative introduction to Pinto
=head1 VERSION
version 0.09992_01
=head1 INTRODUCTION
This tutorial walks you through some of the typical use cases for a L<Pinto>
repository. Along the way, it demonstrates most of the L<pinto> commands.
You are encouraged to try the commands as you read along. If you would prefer
to get a more condensed summary of features and commands, please read the
L<Pinto::Manual::QuickStart>. For detailed instructions on installing the
software read L<Pinto::Manual::Installing>.
=head1 BASIC OPERATIONS
=head2 Creating a repository
The first step in using Pinto is to create a repository, using the
L<init|App::Pinto::Command::init> command like this:
pinto --root ~/repo init
This will create a new repository in the F<~/repo> directory. If that
directory does not exist, it will be created for you. If it already does
exist, then it must be empty.
The C<--root> (or C<-r>) option specifies where the repository is. This
argument is required for every L<pinto> command. But if you get tired of
typing it, you can set the C<PINTO_REPOSITORY_ROOT> environment variable to
point to your repository instead.
The repository is created with a stack called "master" which is also marked as
the default stack. We'll talk more about stacks and default stack later.
=head2 Inspecting the repository
Now that you have a repository, let's look inside it. To see the contents of
a repository, use the L<list|App::Pinto::Command::list> command:
pinto --root ~/repo list
You will use the L<list|App::Pinto::Command::list> command quite often. But
at this point, the listing will be empty because there is nothing in the
repository. So let's go ahead and add something...
=head2 Adding dependencies
Suppose we are working on an application called My-App that contains a package
called C<My::App>. The application also depends on the L<URI> package. Using
the L<pull|App::Pinto::Command::pull> command, you can bring the URI package
into your repository:
pinto --root ~/repo pull URI
You will be prompted to enter a log message that describes why this change is
happening. The message template will include a semi-informative generated
message. Feel free to edit this message as you see fit. Save the file and
close your editor when you are done.
Now, you should have URI in your local repository. So lets look and see what
we really got. Once again, you use the L<list|App::Pinto::Command::list>
command to see inside the repository:
pinto --root ~/repo list
This time, the listing will look something like this:
rf URI 1.60 GAAS/URI-1.60.tar.gz
rf URI::Escape 3.31 GAAS/URI-1.60.tar.gz
rf URI::Heuristic 4.20 GAAS/URI-1.60.tar.gz
...
You can see that the URI package has been added to the repository, as well as
all the prerequisites for URI, and all of their prerequisites, and so on.
=head2 Adding your own distributions
Now suppose that you've finished work on My-App and your ready to release the
first version. Using your preferred build tool (L<ExtUtils::MakeMaker>,
L<Module::Build>, L<Module::Install> etc.) you package a release as F<My-
App-1.0.tar.gz>. Now put the distribution into the repository with the
L<add|App::Pinto::Command::add> command:
pinto --root ~/repo add path/to/My-App-1.0.tar.gz
When you list the repository contents now, it will include the C<My::App>
package and show you as the author of the distribution:
rl My::App 1.0 JEFF/My-App-1.0.tar.gz
rf URI 1.60 GAAS/URI-1.60.tar.gz
rf URI::Escape 3.31 GAAS/URI-1.60.tar.gz
rf URI::Heuristic 4.20 GAAS/URI-1.60.tar.gz
...
=head2 Installing packages
Now the repository contains both your application and all of its
prerequisites, so you can install it into your environment using the
L<install|App::Pinto::Command::install> command:
pinto --root ~/repo install My::App
When C<My::App> is installed, it will only use the prerequisites that are in
your repository. Even if a newer version of URI is released to the CPAN in
the future, C<My::App> will always be built with the same versions of the same
prerequisites that you developed and tested against. This ensures your
application builds will be stable and predictable.
On the surface, a Pinto repository looks like an ordinary CPAN, so you can
also install packages from it using L<cpanm> directly. All you have to do is
point them at the URI of your repository (under the hood, this is all the
L<install|App::Pinto::Command::install> command is really doing anyway). For
example:
cpanm --mirror file:///home/jeff/repo --mirror-only My::App
The C<--mirror-only> flag is important because it tells L<cpanm> to B<not> use
the C<cpanmetadb> to resolve packages. Instead you only want to use the index
from B<your> repository.
You can do the same thing with L<cpan> and L<cpanp> as well. See their
documentation for information on how to set the URI of the repository.
=head2 Upgrading a dependency
Suppose that several weeks have passed since you first released My-App and now
URI version 1.62 is available on the CPAN. It has some bug critical fixes
that you'd like to get. Again, we can bring that into the repository using
the L<pull|App::Pinto::Command::pull> command. But since your repository
already contains a version of URI, you must indicate that you want a *newer*
one by specifying the minimum version that you want:
pinto --root ~/repo pull URI~1.62
If you look at the listing again, this time you'll see the newer version of
URI (and possibly other packages as well):
rl My::App 1.0 JEFF/My-App-1.0.tar.gz
rf URI 1.62 GAAS/URI-1.62.tar.gz
rf URI::Escape 3.38 GAAS/URI-1.62.tar.gz
rf URI::Heuristic 4.20 GAAS/URI-1.62.tar.gz
...
If the new version of URI requires any new prerequisites, those will be in the
repository too. Now when you install C<My::App>, you'll get version 1.62 of
URI.
=head1 WORKING WITH STACKS
So far in this tutorial, we've treated the repository as a singular resource.
For example, when we upgraded URI in the last section, it impacted every
person and every application that might have been using the repository. But
this kind of broad impact is undesirable. You would prefer to make those kinds
of changes in isolation and test them before forcing everyone else to upgrade.
This is what stacks are designed for.
=head2 What is a stack
All CPAN-like repositories have an index which maps the latest version of each
package to the archive that contains it. Usually, there is only one such
index per repository. But with Pinto, there can be many indexes. Each of
these indexes is called a "stack". This allows you to create different stacks
of dependencies within a single repository. So you could have a C<development>
stack and a C<production> stack. Whenever you add a distribution or upgrade a
prerequisite, it only affects one stack.
=head2 The default stack
Before getting into the gory details, you first need to know about the default
stack. For most operations, the name of the stack is an optional parameter.
So if you do not specify a stack explicitly, then the operation is applied to
whichever stack is marked as the default.
In any repository, there is never more than one default stack. When we
created this repository, the C<master> stack was marked as the default. You
can also change the default stack or change the name of a stack, but we won't
go into that here. See the L<default|App::Pinto::Command::default> command to
learn more about that.
Just remember that C<master> is the name of the stack that was created when
the repository was first initialized.
=head2 Creating a stack
Suppose your repository contains version 1.60 of URI, but version 1.62 has
been released to the CPAN, just like in the earlier section. You want to try
upgrading, but this time you're going to do it on a separate stack.
Thus far, everything you've added or pulled into the repository has gone onto
the C<master> stack. You could create an entirely new stack, but the
C<master> stack already has the prerequisites for My-App, so we're just going
to make a clone using the L<copy|App::Pinto::Command::copy> command:
pinto --root ~/repo copy master uri_upgrade
This creates a new stack called C<uri_upgrade>. If you want to see the
contents of that stack, just use the L<list|App::Pinto::Command::list> command
with the C<--stack> option:
pinto --root ~/repo list --stack uri_upgrade
The listing should be identical to the C<master> stack:
rl My::App 1.0 JEFF/My-App-1.0.tar.gz
rf URI 1.60 GAAS/URI-1.60.tar.gz
...
=head2 Upgrading a stack
Now that you've got a separate stack, you can try upgrading URI. Just as
before, you'll use the L<pull|App::Pinto::Command::pull> command. But this
time, you'll tell Pinto that you want the packages to be pulled onto the
C<uri_upgrade> stack:
pinto --root ~/repo pull --stack uri_upgrade URI~1.62
Now lets compare the C<master> and C<uri_upgrade> stacks using the
L<diff|App::Pinto::Command::diff> command:
pinto --root ~/repo diff master uri_upgrade
+rf URI 1.62 GAAS/URI-1.62.tar.gz
+rf URI::Escape 3.31 GAAS/URI-1.62.tar.gz
+rf URI::Heuristic 4.20 GAAS/URI-1.62.tar.gz
...
-rf URI 1.60 GAAS/URI-1.60.tar.gz
-rf URI::Escape 3.31 GAAS/URI-1.60.tar.gz
-rf URI::Heuristic 4.20 GAAS/URI-1.60.tar.gz
The output is similar to the diff(1) command. Records starting with a "+" were
added and those starting with a "-" have been removed.
=head2 Installing from a stack
With URI upgraded on the C<uri_upgrade> stack, you can now try building and
testing our application. All you have to do is run the
L<install|App::Pinto::Command::install> command and point to the right stack:
pinto --root ~/repo install --stack uri_upgrade My::App
This will build My::App using only the prerequisites that are on the
C<uri_upgrade> stack. If the tests pass, then you can confidently upgrade URI
on the C<dev> stack as well.
As mentioned earlier, you can also use L<cpanm> to install modules from your
repository. But when installing from a stack other than the default, you must
append "stacks/stack_name" to the URI. For example:
cpanm --mirror file:///home/jeff/repo/stacks/uri_upgrade --mirror-only My::App
=head1 USING PINS
In the last section, we used a stack to experiment with upgrading a
dependency. Fortunately, all the tests passed. But what if the tests didn't
pass? If the problem lies within My-App and you can quickly correct it, you
might just modify your code, release version 2.0 of My-App, and then proceed
to upgrade URI on the C<master> stack.
But if the issue is a bug in URI or it will take a long time to fix My-App,
then you have a real problem. You don't want someone else to upgrade URI, nor
do you want it to be upgraded inadvertently to satisfy some other prerequisite
that My-App may have. Until the bug is fixed (in either URI or My-App) you
need to prevent URI from being upgraded. This is what pins are for.
=head2 Pinning a package
When you pin a package, that version of the package is forced to stay in a
stack. Any attempt to upgrade it (either directly or via another
prerequisite) will fail. To pin a package, use the
L<pin|App::Pinto::Command::pin> command like this:
pinto --root ~/repo pin URI
If you look at the listing for the C<master> stack again, you'll see something
like this:
...
rl My::App 1.0 JEFF/My-App-1.0.tar.gz
rf! URI 1.60 GAAS/URI-1.60.tar.gz
rf! URI::Escape 3.31 GAAS/URI-1.60.tar.gz
...
The "!" near the beginning of the line indicates the package has been pinned.
Notice every package in the F<URI-1.60.tar.gz> distribution has been pinned,
so it is impossible to partially upgrade a distribution (this situation could
happen when a package moves into a different distribution).
=head2 Unpinning a packages
After a while, suppose you fix the problem in My-App or a new version of URI
is released that fixes the bug. When that happens, you can unpin URI from the
stack using the L<unpin|App::Pinto::Command::unpin> command:
pinto --root ~/repo unpin URI
At this point you're free to upgrade URI to the latest version whenever you're
ready. Just as with pinning, when you unpin a package, it unpins every other
package it that distribution as well.
=head1 USING PINS AND STACKS TOGETHER
Pins and stacks are used together to help manage change during the development
cycle. For example, you could create a stack called C<prod> that contains
your known-good dependencies. Likewise, you could create a stack called
C<dev> that contains experimental dependencies for your next release.
Initially, the C<dev> stack is just a copy of the C<prod> stack.
As development proceeds, you may upgrade or add several packages on the C<dev>
stack. If an upgraded package breaks your application, then you'll place a
pin in that package on the C<prod> stack to signal that it shouldn't be
upgraded.
=head2 Pins and Patches
Sometimes you may find that a new version of a CPAN distribution has a bug but
the author is unable or unwilling to fix it (at least not before your next
release is due). In that situation, you may elect to make a local patch of
the CPAN distribution.
So suppose that you forked the code for L<URI> and made a local version of the
distribution called F<URI-1.60_PATCHED.tar.gz>. You can add it to your
repository using the L<add|App::Pinto::Command::add> command:
pinto --root ~/repo add path/to/URI-1.60_PATCHED.tar.gz
In this situation, it is wise to pin the package as well, since you do not
want it to be updated until you are sure that the new release includes your
patch or the author has fixed the bug by other means.
pinto --root ~/repo pin URI
When the author of URI releases version 1.62 with your patch, you'll want to
try it before deciding to unpin from your locally patched version. Just as
before, this can be done by cloning the stack with the
L<copy|App::Pinto::Command::copy> command. Let's call it the C<trial> stack
this time:
pinto --root ~/repo copy master trial
But before you can update URI on the C<trial> stack, you'll have to unpin it
there:
pinto --root ~/repo unpin --stack trial URI
Now you can proceed to update URI on the stack and try building C<My::App>
like this:
pinto --root ~/repo update --stack trial URI
pinto --root ~/repo install --stack trial My::App
If all the tests pass, then you can merge the changes back to the C<master>
stack:
pinto --root ~/repo merge trial master
=head2 Reviewing Past Changes
As you've noticed by now, each command that changes the state of a stack
requires a log message to describe it. You can review those messages using
the L<log|App::Pinto::Command::log> command:
pinto --root ~/repo log
That should display something like this:
revision 4a62d7ce-245c-45d4-89f8-987080a90112
Date: Mar 15, 2013 1:58:05 PM
User: jeff
Pin GAAS/URI-1.59.tar.gz
Pinning URI because it is not causes our foo.t script to fail
revision 4a62d7ce-245c-45d4-89f8-987080a90112
Date: Mar 15, 2013 1:58:05 PM
User: jeff
Pull GAAS/URI-1.59.tar.gz
URI is required for HTTP support in our application
...
The header for each message shows who made the change and when it happened. It
also has a unique identifier similar to Git's SHA-1 digests. You can use
these identifiers to see the diffs between different revisions or to reset the
stack back to a prior revision [NB: this feature is not actually implemented
yet].
=head1 CONCLUSION
In this tutorial, you've seen the basic L<pinto> commands for pulling
dependencies into the repository, and adding your own distributions to the
repository. You've also seen how to use stacks and pins to manage your
dependencies in the face of some common development obstacles.
Each command has several options that were not discussed in this tutorial, and
there are some commands that were not mentioned here at all. So you are
encouraged to explore the manual pages for each command and learn more.
=head1 SEE ALSO
L<Pinto::Manual::QuickStart>
L<Pinto::Manual::Installing>
L<Pinto> (the library)
L<pinto> (the application)
=head1 AUTHOR
Jeffrey Ryan Thalhammer <jeff@stratopan.com>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2014 by Jeffrey Ryan Thalhammer.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut