mattfarina / pkg

11 stars 3 forks source link

Go Package Specification

This outlines a proposed manner for documenting the metadata associated with a Go package. The current examples use json but that's not a requirement. Instead, the goal is to solve the use cases. Proposed changes are welcome. Ideally, there would be one format for documenting the information that numerous tools (e.g. package managers) could all use and share.

For example,

{
    "name": "github.com/mattfarina/pkg",
    "description": "A package definition for Go.",
    "keywords": ["vendor", "package"],
    "imports": [
        {
            "name": "github.com/Masterminds/vcs"
        },
        {
            "name": "github.com/Masterminds/cookoo",
            "version": "v1.0.0"
        }
    ]
}

Where Inspiration Came From

The ideas outlined in this setup come from other package managers that solved the problems Go is working on. The ideas are are from lessons learned elsewhere. These other package managers include npm (for Node.js), Bower (browser side JavaScript), Composer (for PHP), Crates (for Rust), Bundler and Ruby Gems, and others. This is a well worn path we can learn from.

Of course, much can also be learned from the venerable operating system package managers like RPM and dpkg, which have long been used to manage development libraries.

Use Cases

At this point the focus is on use cases that need to be solved. The requirements are needed to understand what needs to be built and solved. The use cases are currently being documented in the use cases sub-directory.

Package Information

Some information describes the package itself. For example,

{
    "name": "github.com/Masterminds/glide",
    "image": "logo.png",
    "description": "A package manager for Go.",
    "keywords": ["vendor", "package", "VCS"],
    "homepage": "https://example.com",
    "license": "MIT",
    "owners": [
        {
            "name": "Matt Butcher",
            "email": "something@example.com",
            "homepage": "http://technosophos.com"
        },
        {
            "name": "Matt Farina",
            "email": "other@example.com",
            "homepage": "http://mattfarina.com"
        }
    ],
    "imports": []
}

Each of these properties is useful for tools and people who need to know about a project. For example, the homepage property for GB would be http://getgb.io which tells you something about the project not in the code but useful to others. Another example is the license property. It notes an SPDX compatible license or a path to a file with the license. This is useful for tools that need to look over the packages in an application to determine license information.

All of these properties are useful for applications that need to display or otherwise work with information about an application or package that's not part of the source for making the application run.

Importing Dependent Packages

Go packages will often import dependent packages. While working with those dependencies there are cases where you're developing one at the same time as your application, cases for working with forks, and other ways packages need to be handled. To accommodate those details, the import property is an array of objects containing details about a package. For example,

{
    "name": "github.com/Masterminds/glide",
    "imports": [
        {
            "name": "github.com/Masterminds/cookoo",
            "version": "1.2.0",
            "repository": "git@github.com:Masterminds/cookoo.git",
            "type": "git",
        }
    ]
}

Aside from the name field, all the others are optional. If just the name is specified, the package dependent package would be retrieved and the version used would be the latest on the default branch. This is the same version go get retrieves today. With other properties are used the name is the place where the package will be located in the directory tree.

The version field allows you to set a branch, tag, commit id, or Semantic Version constraint (e.g, ">= 1.0, < 1.4").

The repository and type information are useful for a couple reasons including:

  1. The package is in a private repository and you need SSH keys to access it.
  2. Forks. For example what if the repository were git@github.com:mattfarina/cookoo.git.
  3. When you want to checkout a dependency in a manner you can commit and push changes while working on the main project.

Version Locking

When you can support version ranges it's useful to lock to a specific version of each package in the dependency tree. This is supported through the use of a pkg.lock file containing the locking information.

{
    "hash": "2cb908fb4479bec8ed4fb8b6e719207fcf11d97e",
    "updated": "2006-01-02T15:04:05Z07:00",
    "imports":[
        {
            "name": "github.com/Masterminds/semver",
            "version": "6333b7bd29aad1d79898ff568fd90a8aa533ae82"
        },
        {
            "name": "github.com/Masterminds/vcs",
            "version": "eaee272c8fa4514e1572e182faecff5be20e792a"
        }
    ]
}

The hash property is a sha256 hash of the pkg.json file. This is used to make sure the pkg.lock file is in sync with the pkg.json file. When they are not in sync the pkg.lock information should not be used until the pkg.lock is regenerated with the based on the current pkg.json file. updated provides a timestamp in RFC 3339 format for the last time the pkg.lock file was generated.

The imports property is a list of all the packages in the entire dependency tree along with the specific version, as opposed to version range, the project is locked to.

Vendoring?

Vendoring, where the code from the dependent package is stored in the parent projects VCS, can be used with this setup but isn't required. A tool could update vendored code or install the proper code and version in each environment. The way dependencies are stored is left as an exercise for the developer.

Work In Progress

This is currently a work in progress.

Todo: