Documentation
Snow framework specification v1.1
Notice
The primary objective of the Snow framework is to facilitate the sharing of code among users of several Scheme systems. For this reason the current design often takes a "least common denominator" approach to portability. It would be unreasonably restrictive to support all Scheme systems this way. The Snow framework targets the most popular and mature Scheme systems which typically contain many more features than required by the Scheme standard (e.g. modules, FFIs, libraries, etc). In this context the "least common denominator" is actually quite rich. Be advised that the Snow framework specification is expected to evolve to take into account the evolution of these Scheme systems and their level of conformance to the Scheme standards. Experience with the framework and feedback from users and implementors will also fuel changes to the specification. Please join the mailing list if you are interested in participating in any aspect of this effort (including contributing packages, suggesting improvements to the Snow specification, improving the Snow implementations, etc).
Overview
A Snow package is a piece of software offering a certain functionality. This functionality is accessible to other packages through the package's API. A package may depend on the functionality of other packages for its implementation (but circular dependencies are forbidden). The package is identified by a name and version.
A package's API and dependencies are specified using the
package* special form. This form includes the name
and version of the package, the set of named procedures, named
macros, and named record types that are provided by the package,
and the set of packages required for the implementation of the
package. A source code file is composed of a
package* special form at the top followed by the
implementation of the package (procedure definitions and commands).
The name of the source code file containing the package*
special form is derived from the package name with the extension
.scm.
A package contains at least the source code file containing
the package* special form, and possibly other related
files, including source code files (in Scheme or other languages),
data files and documentation.
Here's the source code of a simple package, which is stored in the
file "simple.scm":
(package* simple/v1.0.0
(provide:
(define (square x))
(define (cube x))
(define (inc)))
(require: power/v1))
(define (square x) (fast-expt x 2))
(define (cube x) (fast-expt x 3))
(define counter 0)
(define (inc)
(set! counter (+ counter 1))
counter)
This file specifies the API of package simple
version 1.0.0 in the provide: clause of the
package* form. The file also gives the
implementation of the API in the require: forms and
in the part of the file after the package*
form (this information is used by the generic Snow framework
implementation, and may be used by other Snow framework
implementations).
Package simple version 1.0.0 provides three
procedures: square, cube and
inc. The package power version 1 is
required in the implementation of those procedures (it provides
the procedure fast-expt needed by the implementation
of the procedures square and cube).
The source code of package power, stored in the file
"power.scm", could be:
(package* power/v1.0.0
(provide:
(define (fast-expt x n))))
(define (fast-expt x n)
(cond ((= n 0)
1)
((odd? n)
(* x (fast-expt x (- n 1))))
(else
(fast-expt (* x x) (quotient n 2)))))
Package names
The name of a package is a symbol. Because the name is also used as a
basis for the name of the file containing the package*
special form and portability of file names is an issue, the package
name is restricted as follows:
- It must be composed of lowercase letters (a-z), digits (0-9), the special characters "_" (underscore) and "-" (dash).
- It must start with a lowercase letter or an underscore.
Package version numbers
The version number of a package is a symbol containing 3 non-negative integer fields separated by dots prefixed with "v" i.e. vX.Y.Z . Field X is the major version number, field Y is the minor version number, and field Z is the build number. When a new version of a package is released one of these fields is incremented and the ones that follow, if any, are set to zero. For proper operation of the system a package writer must follow these rules when assigning a version number to a package P:
- Z is incremented when the API is identical to the previous version. This is useful when the implementation of the package changes (for performance reasons, or for repairing bugs). For example, if we assume there are no bugs in versions 2.1.5 and 2.1.9 of package P, then they can be used interchangeably. If there is a choice between packages with identical major and minor version numbers, the one with the greater build version number is normally preferred because it usually includes improvements to the implementation.
- Y is incremented when the API changes in a backward compatible way. This means that the new API offers a strict superset of the API of the previous version. For example, version 2.3.0 of P can be used instead of version 2.Y.Z for Y <= 2 and any Z, because it offers strictly more functionality. If there is a choice between packages with identical major version numbers but different minor version numbers, the one with the greater minor version number is normally preferred because it usually includes improvements to the implementation.
- X is incremented when the API changes in a non-backward compatible way. This means that the API is not a strict superset of the API of the previous version. In other words the two versions are not interchangeable in all contexts (but it may be the case that for some uses of package P the two versions are interchangeable, i.e. when only the functionality in the intersection of their APIs is used). It is possible that two different major versions are completely incompatible (for example when they are designed independently). Because of the general incompatibility of versions with different major version numbers, it is highly recommended that package writers hold off changes in the major version number until absolutely necessary. It is better to adopt a practice of deprecating parts of the API (i.e. marking them as eventually removed from the API) and only removing them, with a change in major version number, when a reasonable amount of time has passed. This leaves time for the maintainers of other packages which depend on P to modify their implementation to avoid the deprecated parts.
Grammar
The file containing the package* special form must obey
the following syntax:
<package-file> --> <package-form> <package-file-body>
<package-form> --> ( package* <package-name-with-full-version>
<package-form-body> )
<package-form-body> --> <package-attributes> <implementation-requirements>
<package-attributes> --> [ <provide-form> ] <package-meta-data>*
<package-meta-data> --> ( maintainer: <string>+ )
| ( author: <string>+ )
| ( homepage: <string>+ )
| ( description: <string>+ )
| ( keywords: <symbol>+ )
| ( license: <symbol>+ )
<provide-form> --> ( provide: <provide-form-body> )
<provide-form-body> --> <interface-definitions>
<interface-definitions> --> <interface-def>*
<interface-def> --> <procedure-prototype>
| <macro-def>
| <record-def>
<procedure-prototype> --> ( define ( <var-name> <R5RS def formals> ) )
| ( define* ( <var-name> <prototype formals> ) )
<procedure-def> --> ( define ( <var-name> <R5RS def formals> )
<body> )
| ( define* ( <var-name> <SRFI 89 extended def formals> )
<body> )
<macro-def> --> ( define-syntax <macro-name>
<expression> )
| ( define-macro ( <macro-name> <R5RS def formals> )
<body> )
| ( define-macro* ( <macro-name> <SRFI 89 extended def formals> )
<body> )
<record-def> --> ( define-record* <record-name>
<record-body> )
<record-body> --> <record-clause>*
<record-clause> --> <record-field>
<record-field> --> <field-name>
<implementation-requirements> --> <requirements>
<requirements> --> <require-form>*
<require-form> --> ( require: <package-name-with-required-version>
<require-form-body> )
<require-form-body> --> <empty>
<package-file-body> --> <package command or def>*
<package command or def> --> <expression>
| <implementation-def>
| <include>
| <test>
<implementation-def> --> <var-def>
| <macro-def>
| <record-def>
<var-def> --> ( define <var-name> <expression> )
| <procedure-def>
<include> --> ( include* <unix-relative-path-string> )
<test> --> ( test* <test-body> )
<test-body> --> <package-file-body-containing-expect-expressions>
<package-file-body-containing-expect-expressions> --> <package-file-body>
<expect-expression> --> ( expect* <expect-test> )
<expect-test> --> <expression>
<field-name> --> <name>
<var-name> --> <name>
<package-name> --> <name>
<package-name-with-full-version> --> <package-name>/<full-version>
<package-name-with-required-version> --> <package-name>/<required-version>
<macro-name> --> <name>
<record-name> --> <name>
<name> --> <identifier>
<full-version> --> v<N>.<N>.<N>
<required-version> --> v<N>
| v<N>.<N>
| v<N>.<N>.<N>
<prototype formals> --> <SRFI 89 extended def formals>
<SRFI 89 extended def formals> --> <extended def formals> from SRFI 89
<R5RS def formals> --> <def formals> from R5RS
Package meta-data
The <package-meta-data> forms give various meta-data
of the package: the maintainer(s), the author(s), the homepage(s), the description
of the package, the license, and the keywords associated with the package.
Although this meta-data is optional, providing it is
recommended since it helps to classify the packages on the
snowfort, and to assign maintenance and authorship
responsibilities. The names of the maintainer(s) and
author(s) are given in the same syntax as the names of
certificates used for signing packages (full name followed by
obfuscated email address, e.g. "John Smith <js at
acme.com>"). The homepages are URLs connected with the
package. The first string of the package description
should fit on one line and summarize the function of the package.
Short symbols should be used to denote the keywords associated with
the package and when possible existing keywords should
be used (e.g. net, crypto,
i/o, etc). Here's an example using all the meta-data
tags:
(package* hello/v1.0.0
(provide:
(define (hello port)))
(maintainer: "Scheme Now! <snow at iro.umontreal.ca>")
(author: "Marc Feeley <feeley at iro.umontreal.ca>")
(homepage: "http://snow.iro.umontreal.ca")
(description: "Display classic greeting.")
(keywords: example i/o)
(license: lgpl/v2.1))
(define (hello port)
(display "hello" port))
Formal parameters of procedures and macros
The <SRFI 89 extended def formals> obeys the
syntax and semantics of SRFI
89 (Optional positional and named parameters). In the case
of a <prototype formals>, the default
expression of any optional parameter in the parameter list must
be replaced with an underscore (the default expression must
however be specified fully in the
<package-file-body>). For example:
(package* hello/v1.0.0
(provide:
(define* (hello (port _)))))
(define* (hello (port (current-output-port)))
(display "hello" port))
Definition of procedures, macros and records
The define and define* forms must be
used to define toplevel procedures and variables in
<package-file-body> and
<interface-definitions>. It is good style to
use define* only when there are optional parameters.
Macros are defined with the define-syntax,
define-macro, and define-macro* forms,
which must appear at the toplevel of
<package-file-body> or
<interface-definitions>. The body of the
define-syntax form must be a
syntax-rules form. The lexical environment of the
syntax-rules depends on the package that require
it (because the macro definition is effectively copied to the top
of each package that requires the package providing the macro
definition). The body of the define-macro and
define-macro* forms is
an expression evaluated at macro expansion time to generate the
S-expression which replaces the macro call. The evaluation
environment of the body is the R4RS global environment extended
with the macro parameters. The global environment must not be
mutated. Package writers are advised that define-syntax is
currently less portable than define-macro. To maximize
portability macros should be defined with define-macro and
define-macro* when possible.
Records are defined with the define-record* form,
which must appear at the toplevel of
<package-file-body> or
<interface-definitions>. The arguments are,
in order, the name of the record type and the name of each field.
The define-record* form defines a record
constructor, a type predicate, and getter and setter procedures
for each field. The names of these procedures are generated as
follows for the declaration (define-record* R
A B):
-
(make-R a b): construct an instance of record type R whose fields are initialized with the parameters. -
(R? obj): return a boolean indicating if the objectobjis an instance of record type R. -
(R-A r): return the content of field A ofrwhich must be an instance of record type R. -
(R-A-set! r x): storesxin field A ofrwhich must be an instance of record type R. An unspecified result is returned. -
(R-B r): getter of fieldB. -
(R-B-set! r x): setter of fieldB.
The provide: clause of the package*
form defines the API of the package. It can contain
define forms, define* forms,
define-syntax forms, define-macro forms,
define-macro* forms, and define-record*
forms. In the case of define, define*,
define-syntax, define-macro and
define-macro* forms, the identifier being defined is
exported by the package. In the case of define-record*
forms, all the procedures generated are exported by the package. All
names exported by a package are accessible in the
<package-file-body>.
Here is an example combining various forms of definitions:
(package* mixed/v1.0.0
(provide:
(define* (put (x _) (y _)))
(define (get))
(define (add))
(define (sub))
(define-record* point x y)
(define-syntax pop
(syntax-rules ()
((pop var)
(let ((top (car var)))
(set! var (cdr var))
top))))
(define-macro (push val var)
`(set! ,var (cons ,val ,var)))))
(define stack '())
(define* (put (x 0) (y 0))
(push (make-point x y) stack))
(define (get)
(pop stack))
(define (binary-op fn)
(let* ((b (pop stack))
(a (pop stack)))
(push (make-point (fn (point-x a) (point-x b))
(fn (point-y a) (point-y b)))
stack)))
(define (add) (binary-op +))
(define (sub) (binary-op -))
cond-expand form
The cond-expand form specified by SRFI
0 (Feature-based conditional expansion construct) can appear
inside a <package-form-body>,
<provide-form-body>, and
<package-file-body>. Inside a
<package-form-body> and
<provide-form-body>, the
cond-expand forms may only test the host Scheme
system (currently one of the symbols
bigloo,
chez,
chicken,
gambit,
gauche,
guile,
kawa,
larceny,
mit,
mzscheme,
petite,
scheme48,
scm,
scsh,
sisc,
stalin,
and stklos). Inside a
<package-file-body> the
cond-expand forms may test the host Scheme
system and any other feature.
include* form
When used in a <package-file-body>, the
include* form is equivalent to a begin
form whose body is the sequence of expressions in the file
referred to by <unix-relative-path-string>. A
<unix-relative-path-string> is a string
expressing with the Unix notation a path relative to the current
file. This path will be converted to the OS specific path when
the file is included. The path must not be empty or start or end
with a slash. Repeated slashes are treated like a single slash.
For portability the path must contain only the following ASCII
characters: lowercase letters, digits, ".", "/", "_",
"-", and " " (space).
test* and expect* forms
The test* form can be used to include self test code
within the package body. The definitions and expressions inside
test* forms are normally ignored. However,
test* forms are transformed into begin forms
when they are activated in a Snow framework implementation specific
way (for example the SNOW_TEST environment variable
of the generic Snow framework implementation).
The expect* form can only appear inside
test* forms. The expect* form
takes a single argument, an expression whose value is expected
to be non-false. The test is said to have failed when the
expression evaluates to #f or an exception is raised.
The result of the self tests (number of failed
tests, etc) is reported in a system dependent way.
For example:
(package* math/v1.0.0 (provide: (define (square x)))) (define (square x) (* x x)) (test* (define (dec n) (- n 1)) (expect* (= 100 (square 10))) (expect* (= 81 (square (dec 10)))))
Package dependencies
The packages required by a package are indicated in
require: forms (one per required package). Each
package requirement specifies with a
<required-version> a set of package versions
that are acceptable. Only one version from this set will be
linked with the package (i.e. the linked package). The
major version of the linked package will match the major version
of the <required-version>. If the
<required-version> has a minor version number
then the linked package will have at least that minor version
number. If the <required-version> has minor
and build version numbers then the linked package will have at
least that minor version number, and if the minor versions are
equal, at least that build version number. Among the versions of
a package that are installed, the one with the highest acceptable
version number will be used. It is an error if none of the
installed versions of a package are acceptable.
Package distribution specification
Snowball structure
In general Snow packages are composed of a set of files. These files
are packaged into a compressed archive (snowball). The
snowball for package P version vX.Y.Z is a file in
".tar.gz" format containing all the files of the package organized in
a hierarchy. It contains the root directory P/vX.Y.Z in which
there is a snow subdirectory and possibly subdirectories
associated with each specialized Snow implementation
(i.e. bigloo, chez, chicken,
gambit, etc).
The snow subdirectory contains at least the file
P.scm which contains the
package* special form (with the package name
P/vX.Y.Z). Generic data files needed by the package are
normally put in the snow subdirectory. The
snow subdirectory may also contain the executable
scripts install-script.sh and
install-script.bat (either one or both). If
present, the script will be run when the package is
installed (install-script.sh is run on Unix
environments and install-script.bat is run on
Windows environments). The script is run with a current
directory equal to the snow subdirectory. The
package installation will be aborted if the script exits with a
non zero status.
For example, here are the steps for creating the snowball
mypkg-v1_0_0.tar.gz:
% mkdir mypkg % mkdir mypkg/v1.0.0 % mkdir mypkg/v1.0.0/snow % cat > mypkg/v1.0.0/snow/mypkg.scm (package* mypkg/v1.0.0 (provide: (define (hello)))) (define (hello) (display "hello")) % tar cf mypkg-v1_0_0.tar mypkg/v1.0.0 % gzip -9 mypkg-v1_0_0.tar
The specialized subdirectories of the root directory are reserved for the use of the corresponding specialized Snow implementation. Please consult the documentation of those implementations for details.
Snowfort protocol
The central package repository (snowfort) will use the
filename P-vX_Y_Z.tgz to store the snowball of
package P version vX.Y.Z. For convenience it is not
necessary to include the package version number subdirectory when
uploading a snowball to the snowfort. However, when a snowball is
downloaded from the snowfort, the snowball format specified above will
be used (if needed the snowball structure will be changed to put all
files in the directory P/vX.Y.Z).
Packages can be uploaded,
downloaded and browsed manually (with a web browser and utility
programs such as
wget and curl) or with the Snow package manager
utility (snowman) which includes package signing and
verification features. The HTTP protocol is used to interact with the snowfort.
A transaction with the snowfort is implemented by an HTML form with an
"operation" field indicating the operation requested. Three operations
are supported: "list" (get the list of packages), "download"
(get a snowball from the snowfort), and "upload" (transfer a snowball
to the snowfort).
Here is how a minimal snowball for the package "mypkg" given above can be created, uploaded and downloaded manually from the snowfort, and how a list of packages can be obtained:
% mkdir mypkg
% mkdir mypkg/snow
% cat > mypkg/snow/mypkg.scm
(package* mypkg/v1.0.0
(provide: (define (hello))))
(define (hello) (display "hello"))
% tar cf mypkg.tar mypkg
% gzip -9 mypkg.tar
% curl -s -F operation=upload -F snowball=@mypkg.tar.gz http://snow.iro.umontreal.ca
<html><!-- upload success
snowball mypkg-v1_0_0.tgz uploaded successfully!
...
% curl -s -F operation=download -F pkg=mypkg/v1.0.0 http://snow.iro.umontreal.ca | gunzip -c | tar tf -
mypkg/v1.0.0/
mypkg/v1.0.0/snow/
mypkg/v1.0.0/snow/mypkg.scm
% curl -s -F operation=list http://snow.iro.umontreal.ca
("extio" (package* extio/v1.0.0 ...
("cert" (package* cert/v1.0.0 ...
...
Using Snow
Specialized Snow implementations
Each specialized Snow implementation defines how Snow is used with that implementation. It is expected that the usage procedures will roughly follow those of the generic Snow implementation with extensions that address implementation dependent issues.
Please see the Links for a list of the known specialized Snow framework implementations.
Generic Snow implementation
With the generic Snow framework, package management (installation,
uploading, signing, etc) is performed with the snowman
utility program. The usage summary follows:
Usage:
% snowman list <- list packages available on snowfort
% snowman install foo <- install highest version of foo
% snowman install foo/v2 <- install highest revision of foo/v2
% snowman install foo bar baz <- install packages foo, bar and baz
% snowman install xyz/foo.tgz <- install snowball stored in a file
% snowman download foo <- download highest version of foo
% snowman upload xyz/foo/v2.0.0 <- upload .tgz of revision 2.0.0 of foo
% snowman upload xyz/foo <- upload .tgz of highest version of foo
% snowman upload xyz/foo.tgz <- upload snowball stored in a file
% snowman cert-passwd <- change certificate file password
% snowman cert-create <- create new certificate for signing
% snowman cert-export [<file>] <- save/display certificate (in ASCII)
% snowman cert-import <file|cert>* <- add/replace certificate (from ASCII)
% snowman cert-remove <- remove certificate
% snowman cert-list <- list certificates
% snowman verify <file|cert>* <- verify regular files or certificates
% snowman sign <file|cert>* <- sign regular files or certificates
Sets of suboptions (first one of each choice set is the default):
list: none
install: --verify|--skip-verify --highest|--exact --user|--site
download: --verify|--skip-verify --highest|--exact
upload: none
cert-passwd: none
cert-create: none
cert-export: --user|--site|--sign
cert-import: --user|--site|--sign
cert-remove: --user|--site|--sign
cert-list: --user|--site|--sign
verify: --user|--site
sign: --sha-1|--sha-224|--sha-256
Installing packages with snowman
The "snowman install P" command is the easiest way
to install package P from the snowfort. An HTTP request will be
sent to the snowfort to get the snowball of the highest available version
of package P, the files in the snowball will be checked for
valid digital signatures, and finally the snowball will be installed
in the file system. Multiple packages can be installed by putting all
of the package names on the command line. For example:
% snowman install simple power
A version of package P can be requested by adding the version to the package name, i.e. P/vX, P/vX.Y, or P/vX.Y.Z. By default, the highest available version compatible with the requested version will be installed. For example:
% snowman install simple/v1
To install a specific version the option --exact must be used
and the full version must be requested, i.e. P/vX.Y.Z.
For example:
% snowman install --exact simple/v1.0.0
The installation will be aborted if some files in the snowball
have invalid signatures or they are signed by people you have not
chosen to trust by adding their certificate to your trusted
certificate database. Note that the certificate of "Scheme Now!
<snow at iro.umontreal.ca>" is implicitly trusted so all
of the Snow core packages can be installed securely with no extra
configuration steps. To skip the signature verification, use the
option --skip-verify. For example:
% snowman install --skip-verify simple
The package is normally installed in the user specific directory which
was chosen when the generic Snow framework implementation was installed
($HOME/.snow by default). Use the option
--site to install in the site-wide directory, which
is typically accessible to all users and is
/usr/share/snow by default. For example:
% snowman install --site simple
If a snowball is stored in a file F with extension
.tgz or .tar.gz, then it can be
installed with the command "snowman install
F". This is useful for package maintainers to locally
test the installation of a snowball before it is uploaded to the
snowfort. It is also useful in combination with the
"snowman download P" command which only
downloads the snowball from the snowfort. For example:
% snowman download simple % snowman install simple-v1_0_0.tgz
Package signing, verification and uploading with snowman
The Snow framework uses digital signatures to authenticate snowballs. In this approach each Snow package maintainer is assigned a certificate pair composed of a private and public certificate. The package maintainer must keep the private certificate secret and give the public certificate to anyone needing to authenticate the maintainer's snowballs (this can be done in person, via email, putting it on a public web page, etc). The package maintainer uses the private certificate to sign snowballs. The installers of a snowball use the maintainer's public certificate to verify the snowball's authenticity. It is the responsibility of the installers to take the appropriate measures to ensure that the public certificate they obtain really is assigned to the maintainer.
To simplify the management of certificates by snowman
the Snow framework maintains three certificate databases:
- A database of public certificates trusted by the site
(
/usr/share/snow/.publ-certsby default). This database is selected with the--siteoption. - A database of public certificates trusted by the user
(
$HOME/.snow/.publ-certsby default). This database is selected with the--useroption, which is the default. - A database of the certificate pairs of the user
(
$HOME/.snow/.priv-certsby default). This database is selected with the--signoption. This database is encrypted for security reasons.
Several commands are available to manage the certificate databases.
The "snowman cert-create P" command creates
a new certificate pair and stores it in the user's database of
certificate pairs. The certificate is identified with a
name, an optional comment, and an email address (e.g.
"Marc Feeley <feeley at iro.umontreal.ca>"). The command
will request this information interactively. The size of the RSA key
and the certificate's validity period must also be entered. For example:
% snowman cert-create Enter certificate identification information: Full Name (e.g. Joe Smith): Eliza Hacker Comment (e.g. secondary): Email Address (e.g. js@foo.us): eliza@gmail.com Enter RSA key size in bits (512=default, 1024 or 2048): 1024 Enter validity period, e.g. 14d (14 days) or 1y (1 year=default): 2y Generating an RSA key pair (this may take a few minutes)... .+++++++++ +++++ .++++++ You do not have a private certificate file. This file is an encrypted file which stores the private certificate(s) which you can use to sign the files in Snow packages. It is encrypted for security, so that only you can use the certificate(s). You must enter a password for encrypting the private certificate file. Please enter access control password for file /u/eliza/.snow/.priv-certs: Password: zzz111@@@ Please enter the password again. Password: zzz111@@@
Note that the user's database of certificate pairs is password
protected. The password must be at least 8 characters long and
contain at least one letter, one digit, and one special
character. To change the password the "snowman
cert-passwd" command must be used.
% snowman cert-passwd Please enter access control password for file /u/eliza/.snow/.priv-certs: Password: zzz111@@@ Please enter new access control password for file /u/eliza/.snow/.priv-certs: Password: ejkdi34! Please enter the password again. Password: ejkdi34! Password was changed successfully.
The "snowman cert-list" command displays the content
of one of the certificate databases (the user's public certificates
by default). Use the --site and --sign options
to select a different database. For example:
% snowman cert-list --sign
Please enter access control password for file /u/eliza/.snow/.priv-certs:
Password: ejkdi34!
1) "Eliza Hacker <eliza at gmail.com>"
fingerprint: 5e57ea66239b1909c6f3a007884f931c
purpose: (snow)
authenticity: *** not signed ***
The "snowman cert-export" command displays a
public certificate in ASCII form. Conversely, the
"snowman cert-import" command adds to a
certificate database the public certificate given on the command
line. A package maintainer must use the "snowman
cert-export --sign" command to produce the public
certificate in a form that can be distributed to the package
installers. The ASCII form of a certificate always starts with
the characters "Y2Vyd". The package installers must use the
"snowman cert-import Y2Vyd..." command to add
the certificate to their database of trusted public certificates.
For example:
% snowman cert-export --sign
Please enter access control password for file /u/eliza/.snow/.priv-certs:
Password: ejkdi34!
The certificate "Eliza Hacker <eliza at gmail.com>" will be exported.
Y2VydC12MQoiRWxpemEgSGFja2VyIDxlbGl6YSBhdCBnbWFpbC5jb20+IgooIlJlUjE2dz09IiAiU2FiYzZ3PT0iKQooc25vdykKKDEwMjQgIkFYSkhpWlI2Nzc2MnFqQ0tqMkNPejdDM24wc3lIRkZyeFVvcVBOdFVTWFBnQWluMW5GVVpVL1h3ZjhocCszOTM1ejNucVNvblNxMEd1QmJ6cDVwK0NJL3BPRms5UHhxSkZobUNNS1c4UkoxaW5DNGJnTytXZ1Z3WU9Eam9jVEJBcU9qRm5maDgwdk5YNXNXeUZscDVTOGgwSUJMYnljdUhCUi9EMUN5SWlBM2YiICJBUUFCIikKI2YK
% snowman cert-import Y2VydC12MQoiRWxpemEgSGFja2VyIDxlbGl6YSBhdCBnbWFpbC5jb20+IgooIlJlUjE2dz09IiAiU2FiYzZ3PT0iKQooc25vdykKKDEwMjQgIkFYSkhpWlI2Nzc2MnFqQ0tqMkNPejdDM24wc3lIRkZyeFVvcVBOdFVTWFBnQWluMW5GVVpVL1h3ZjhocCszOTM1ejNucVNvblNxMEd1QmJ6cDVwK0NJL3BPRms5UHhxSkZobUNNS1c4UkoxaW5DNGJnTytXZ1Z3WU9Eam9jVEJBcU9qRm5maDgwdk5YNXNXeUZscDVTOGgwSUJMYnljdUhCUi9EMUN5SWlBM2YiICJBUUFCIikKI2YK
Importing certificate "Eliza Hacker <eliza at gmail.com>"
fingerprint: 5e57ea66239b1909c6f3a007884f931c
purpose: (snow)
authenticity: *** not signed ***
Add certificate "Eliza Hacker <eliza at gmail.com>" (y/n)? y
Certificate was imported successfully.
The "snowman cert-list" command displays the
content of a certificate database.
The "snowman cert-remove" command must be used to
remove a public certificate from a certificate database.
For example:
% snowman cert-list
1) "Eliza Hacker <eliza at gmail.com>"
fingerprint: 5e57ea66239b1909c6f3a007884f931c
purpose: (snow)
authenticity: *** not signed ***
% snowman cert-remove
The certificate "Eliza Hacker <eliza at gmail.com>" will be removed.
Removing certificate "Eliza Hacker <eliza at gmail.com>"
fingerprint: 5e57ea66239b1909c6f3a007884f931c
purpose: (snow)
authenticity: *** not signed ***
Are you sure (y/n)? y
Certificate was removed successfully.
Each file contained in a snowball may be signed using one or more
certificates, or be left unsigned. The signatures for file
F are stored in file F.sig (the
.sig file extension is reserved for signature files
and should not be used for regular files). The signature files
must be created in a package's directory before that package's
snowball is created. This is done with the "snowman
sign P" command where P is the package's
directory. For example:
% mkdir mypkg % mkdir mypkg/v1.0.0 % mkdir mypkg/v1.0.0/snow % cat > mypkg/v1.0.0/snow/mypkg.scm (package* mypkg/v1.0.0 (provide: (define (hello)))) (define (hello) (display "hello")) % snowman sign mypkg Please enter access control password for file /u/eliza/.snow/.priv-certs: Password: ejkdi34! The certificate "Eliza Hacker <eliza at gmail.com>" will be used for signing. Signing mypkg/v1.0.0/snow/mypkg.scm -- done! % tar cf mypkg-v1_0_0.tar mypkg/v1.0.0 % gzip -9 mypkg-v1_0_0.tar % gunzip -c mypkg-v1_0_0.tar.gz | tar tf - mypkg/v1.0.0/ mypkg/v1.0.0/snow/ mypkg/v1.0.0/snow/mypkg.scm mypkg/v1.0.0/snow/mypkg.scm.sig
The "snowman sign P" command creates or
updates the signature files for all the regular files in directory
P. If a signature file already exists for a file, then a
signature is added to the signature file and any inconsistent signature
is removed from the signature file. It is thus possible for several
maintainers to sign the files in a snowball before it is uploaded
to the snowfort (the first maintainer signs the files and sends a snowball
to the second maintainer which also signs the files, etc).
The verification of the files in a snowball is done by default by
the "snowman install P" command. The
verification of regular files can also be done explicitly using
the "snowman verify F" command. If
F is a regular file then only that file is verified (using
the signature file F.sig). If F is a
directory then all the regular files it contains are verified.
For example:
% snowman verify mypkg
mypkg/v1.0.0/snow/mypkg.scm -- signed by: ("Eliza Hacker <eliza at gmail.com>")
mypkg passed verification.
The verification will fail if any of the files does not have
a valid signature. In this case the command may suggest
which certificates can be imported into the trusted certificate
database (with the "snowman cert-import Y2Vyd..."
command) to allow the verification to succeed. This should be
done with caution. You should independently verify the
authenticity of the certificate before importing it.
For example:
% snowman verify mypkg
mypkg/v1.0.0/snow/mypkg.scm -- invalid signature by: ("Marc Feeley <feeley at iro.umontreal.ca>")
mypkg failed verification.
The verification may succeed if one of the following certificates is imported:
1) "Marc Feeley <feeley at iro.umontreal.ca>"
fingerprint: 63c885d801e3bcfe53e773dc199ec481
purpose: (snow)
authenticity: issued by "Scheme Now! CA <snow at iro.umontreal.ca>"
ASCII: Y2VydC12MQoiTWFyYyBGZWVsZXkgPGZlZWxleSBhdCBpcm8udW1vbnRyZWFsLmNhPiIKKCJSZGNNYVE9PSIgIlI3Zy82UT09IikKKHNub3cpCig1MTIgIkFWbnRqQ3ZTL3J4cXJRM0hOOHdaSE9iN3cyRG1DTEpqcXIvRFJDS1pLWVJNRjJaYUdScGRiSXpzWGovS2pFVUVzMHZEN1d2ek45cVlOVHdSSUNQYlVXOD0iICJBUUFCIikKKChjZXJ0LXYxICJTY2hlbWUgTm93ISBDQSA8c25vdyBhdCBpcm8udW1vbnRyZWFsLmNhPiIgKCJSZGNIVlE9PSIgIldLTUtWUT09IikgKHNub3cgY2EpICgxMDI0ICJBZFVPZExoTENXdzBTNHVDS05uZlZ5TzR5VXZBMDBaR1lxOHBKNlBaZFRmNlZ5YnZ5Tm5DYjdZd0krcnlFQ3kyUGRIRmRIVTZoWHpBb0lLazErKytqTi9LV1J1cElIOW9CdjhVYXhZald1ZE1KTkJVeDJBTXBTVFdxYXRYbFVzVXA2L0tndFhtRHpDRnUycXk1RXVJejhUeG51QXBXK1VJemN0dUxLNTlqajBwIiAiQVFBQiIpICNmKSBzaGEtMSAiYTdhMDA0NmIzNjQwYWM4MGIzNjkxZjljMTlhZWZmMTkyMzU5ZWYyYyIgIjRkNjg3ZjVkNDQwMzZjM2M4M2YzZjk4ZTQzOWIxMjlhN2M1NzYzMGYzNWY1YTY2YjlkZjMxMjZmY2VlMmQxMTJjNDU5MWUyNzA4NDY4NmE5OWU2MDQxN2IxNWJlNzQ3ZmRkZTRkOWUyOGJkMmFhNDdiMGQ4NDMyNmI2ODZhNWFlOTkwN2UzNGNkZDBkNDcxNWQzOGM1YjA0YWZiZjNmYjI0OTAxY2FjMjQwN2RmMWIzOWViYjEzMmEwNTZjMzU0NTYxMjcyZmFkYmQ0MDA2ODhjNjAzNTY4NDUxZDNlMTRkMDYwYWNmNjdiNWRhMTc3OGE0YmU2OGJlMjI2ZWRmY2EiKQo=
Snowballs can be uploaded to the snowfort using the
"snowman upload P" command. P is
either a snowball with extension .tgz or
.tar.gz, or a package directory.
For example:
% snowman upload mypkg Upload success: snowball mypkg-v1_0_0.tgz uploaded successfully!
Package maintenance
The Snow package maintenance model identifies packages using a name and a major version number, e.g. P/vX. The various revisions (or instances) of the package are identified with that name and major version number, and some minor version number and build number. Maintenance responsibilities for maintainers only encompass the revisions of a package with a specific major version number. This view is consistent with the fact that the maintainer is responsible for preserving backward compatibility within a given major version number throughout the life of that package. Given that there is no required relation between the APIs of packages with the same name and different major version number, they are considered independent and can be maintained by different sets of maintainers.
The Snow framework realizes this model by allowing anyone to
upload a snowball for package P/vX, and become its
maintainer, if no package with that name and major version number
currently exists on the snowfort. If P/vX exists then the
current maintainers are the only ones permitted to upload
revisions, and only if at least one of them signs the files in
the snowball. Maintainers are identified using their public
certificates. The current maintainers of package P/vX are
indicated in the package* form of the highest
revision of the package (P/vX.Y.Z). If a revision
adds a new maintainer to the package* form, then
that maintainer must sign the package in addition to one of
the current maintainers.
It is possible for a package P/vX to have no maintainer,
either because the package* form does not contain a
maintainer meta-data clause, or because none of the persons
indicated in the maintainer meta-data clause have signed the last
revision. In this case there are no restrictions on the
uploading of revisions. Note that anyone could become the
maintainer of such a package by uploading a signed revision
with a package* form containing their name in the
maintainer meta-data clause.
Script syntax
The generic Snow can be used to write scripts. A script has the
same syntax as a package, except the first line is a shell command
which executes the program "snow".
Here's the source code of a minimal script, which is stored in the
file "go.scm":
":";exec snow -- "$0" "$@"
(package* go/v1.0.0
(require: hostos/v1)
(require: fixnum/v1))
(define (double x)
(snow-fxarithmetic-shift-left x 1))
(test* (expect* (= 8 (double 4))))
(write (double (string->number (cadr (snow-command-line)))))
(newline)
Scripts are normally executed with the host Scheme system which
was configured when the generic Snow implementation was
installed. This can be changed by setting the
SNOW_HOST environment variable to the name of the
required host Scheme system (i.e. bigloo,
chez, chicken, gambit,
etc). The special name all will execute the
script with all the installed Scheme systems. This is useful for
testing the portability of a package. The environment variable
SNOW_TEST can be set to the name(s) of the package(s)
whose self tests must be executed. The environment variable
SNOW_DEBUG when set to 1 will cause some debugging
information to be output. For example:
% chmod +x go.scm % ./go.scm 500 1000 % SNOW_HOST=mzscheme ./go.scm 500 1000 % SNOW_HOST=all ./go.scm 500 ------------------------------------------------------------ bigloo 1000 ------------------------------------------------------------ chicken 1000 ------------------------------------------------------------ gambit 1000 ------------------------------------------------------------ mzscheme 1000 ------------------------------------------------------------ petite 1000 ------------------------------------------------------------ scm 1000 ------------------------------------------------------------ scsh 1000 ------------------------------------------------------------ stklos 1000 % SNOW_TEST="fixnum go" SNOW_HOST=mzscheme ./go.scm 500 1000 *** SNOW TESTS: passed all 7 tests.
The SNOW_PATH environment variable contains the
colon separated list of directories that will be searched to find
packages. If SNOW_PATH is not set, the default
is to search in order: the current directory, the user specific
directory, and the site specific directory. When the package
P is searched, for each directory D
in SNOW_PATH the following files will be
visited to find the package file:
- D
/P.scm - D
/P/snow/P.scm - D
/P/vX.Y.Z/snow/P.scm
Compiling packages
On systems which allow dynamic loading of compiled code (such as
Chicken, Gambit and STklos), individual packages can be compiled
separately with the command "snow --compile ...".
For example, to compile the package foo contained in
the source code file foo.scm using the Gambit system,
the following can be done:
% SNOW_HOST=gambit snow --compile foo.scm