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:

  1. It must be composed of lowercase letters (a-z), digits (0-9), the special characters "_" (underscore) and "-" (dash).
  2. 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:

  1. 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.
  2. 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.
  3. 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):

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:

  1. A database of public certificates trusted by the site (/usr/share/snow/.publ-certs by default). This database is selected with the --site option.
  2. A database of public certificates trusted by the user ($HOME/.snow/.publ-certs by default). This database is selected with the --user option, which is the default.
  3. A database of the certificate pairs of the user ($HOME/.snow/.priv-certs by default). This database is selected with the --sign option. 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:

  1. D/P.scm
  2. D/P/snow/P.scm
  3. 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