# = TITLE:
#
# Project
#
# = COPYING:
#
# Copyright (c) 2007 Psi T Corp.
#
# This file is part of the ProUtils' Ratch program.
#
# Ratch is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ratch is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ratch. If not, see .
require 'facets/filelist'
require 'ratch/metadata/information'
module Ratch
# = Project class
#
# The Project class contains three sets of metadata. The first is the
# typical general information about a project --title, description, homepage, etc.
# This information is essentially static. Once set, it will probably never vary.
# The second is default packaging informaiton. These both come from the General base
# class. The third set is default build information, which may vary well for various
# packages types, as this information may need to be overridden for specific
# platform builds and package formats. This information describes how to
# pack this project into various packages. Hecne it does not belong to the Package
# class itself.
#
# General class is the base class of both the Project and Package classes.
# The class contains typical general information about a project/package, such
# as title, description, homepage, etc. This information is essentially static.
# Once set, it will probably will never vary. The class also contains *defaults*
# for package informaiton --information that is often static, but may vary
# for a partciular package platform or format. A good example is the platform
# attribute.
class Project < Information
PROJECT_FILE = '{,meta/}{project,package,index}{.yaml,.yml,}'
def self.load
location = Dir.pwd # ???
file = Dir.glob(PROJECT_FILE, File::FNM_CASEFOLD).first
if file
data = YAML::load(File.open(file))
new(location, data)
else
raise LoadError, "project file required -- #{PROJECT_FILE}"
end
end
# New Project.
def initialize(location, data={})
@location = location
super(data)
end
# Location is needed to calculate some conventional defaults.
attr_accessor :location
# General
#------------------------------------------------------------------------
# The title of the project (free-form, defaults to name).
attr_accessor :title do
@title || (
name if respond_to?(:name)
)
end
# Subtitle is limited to 60 characters.
attr_accessor :subtitle do
@subtitle.to_s[0..59]
end
# Brief one-line description of the package (Max 80 chars.)
attr_accessor :summary, :brief do
if @summary
@summary.to_s[0..79]
else
i = @description.index('.') || 79
i = 79 if i > 79
@description[0..i]
end
end
# More detailed description of the package.
attr_accessor :description
# "Unix" name of the project/package.
attr_accessor :name
# "Unix" name of master project this "sub-project" may belong.
# (Default is the same as name).
attr_accessor :project do
@project || name
end
# The date the project was started.
attr_accessor :created
# Copyright notice.
attr_accessor :copyright do
@copyright || "Copyright (c) #{Time.now.strftime('%Y')} #{author}"
end
# Distribution License.
attr_accessor :license do
@license || 'GPLv3'
end
# Slogan or "trademark" phrase.
attr_accessor :slogan
# General one-word software category.
attr_accessor :category
# Author(s) of this project.
# (Usually in "name " format.)
attr_accessor :author
# Contact(s) (defaults to authors).
# TODO Move to Variants?
attr_accessor :contact do
@contact || author
end
# Gerneral email address.
attr_accessor :email
# Official domain associated with this package.
attr_accessor :domain
# Project's homepage.
attr_accessor :homepage
# Project's development site.
attr_accessor :development, :devsite
# Internet address(es) to documentation pages.
attr_accessor :documentation, :docs
# Internet address(es) to downloadable packages.
attr_accessor :download
# Internet address for project wiki.
attr_accessor :wiki
# Project's mailing list.
attr_accessor :userlist, :mailinglist, :list
# Developer's mailing list.
attr_accessor :devlist do
@devlist || @userlist
end
# Returns a standard taguri id for the library and release.
def project_taguri
"tag:#{name}.#{domain},#{created}" # or released?
end
# # Version
# #------------------------------------------------------------------------
#
# # Version number (eg. '1.0.0').
# attr_accessor :version
#
# # Current version code name.
# attr_accessor :codename
#
# # Build number can br set to an arbitrar number, or if set to true,
# # it will defaults to a number based on current date-time.
#
# attr_accessor :buildno do
# @buildno = Time.now.strftime("%y%m%d%H%M") if TrueClass === @buildno
# @buildno
# end
# Content Classification
#------------------------------------------------------------------------
# Files in this package that are executables.
# These files must in the packages bin/ directory.
# If left blank all bin/ files are included.
attr_accessor :executable, :executables do
return [@executable].flatten.compact if @executable
exes = []
dir = File.join(location, 'bin')
if File.directory?(dir)
Dir.chdir(dir) do
exes = Dir.glob('*')
end
end
@executable = exes
end
# Library files in this package that are *public*.
# This is akin to load_path but specifies specific files
# that can be loaded from the outside --where as those
# not listed are considerd *private*.
#
# NOTE: This is not enforced --and may never be. It
# complicates library loading. Ie. how to distinguish public
# loading from external loading. But it something that can be
# consider more carfully in the future. For now it can serve
# as an optional reference.
attr_accessor :library, :libraries do
[@library || 'lib/**/*'].flatten
end
# Location(s) of executables.
attr_accessor :bin_path, :bin_paths
# Root location(s) of libraries (used by Rolls).
# If you plan to support Gems, this would be something like:
#
# 'lib/facets'
#
# If not, then the default ('lib') is nice b/c it means one less
# layer in your project heirarchy.
attr_accessor :lib_path, :lib_paths, :load_path, :load_paths do
[@lib_path || 'lib'].flatten
end
# Traditional load path (used by RubyGems).
# The default is 'lib', which is usually fine.
attr_accessor :gem_path, :gem_paths do
[@gem_path || 'lib'].flatten
end
# Security
#------------------------------------------------------------------------
# Encryption digest type used.
# (md5, sha1, sha128, sha256, sha512).
attr_accessor :digest do
@digest || 'md5'
end
# Public key file associated with this library. This is useful
# for security purposes especially remote loading. [pubkey.pem]
attr_accessor :public_key do
@public_key || 'pubkey.pem'
end
# Private key file associated with this library. This is useful
# for security purposes especially remote loading. [_privkey.pem]
attr_accessor :private_key
# @private_key || '_privkey.pem'
# end
# Source Management
#------------------------------------------------------------------------
# Specify which verison control system is being used.
# Sometimes this is autmatically detectable, but it
# is better to specify it.
# Specifices the type of revision control system used.
# darcs, svn, cvs, etc.
# Will try to determine which version control system is being used.
attr_accessor :scm do
return @scm unless @scm.nil?
@scm = if File.directory?('.svn')
'svn'
elsif File.directory?('_darcs')
'darcs'
else
false
end
end
# Files that are tracked under revision control.
# Default is all less standard exceptions.
# '+' and '-' prefixes can be used to augment the list
# rather than fully override it.
attr_accessor :track, :scm_files
# Internet address to source code repository.
# (http://, ftp://, etc.)
attr_accessor :repository, :repo
# Changelog file.
attr_accessor :changelog
# Manifest file. Defaults to 'MANIFEST'.
# (I like to put it in meta/MANIFEST, personally.)
attr_accessor :manifest do
@manifest ||= 'MANIFEST'
end
# Dependencies
#------------------------------------------------------------------------
# Package inter-relationship data. Generally refered to as package
# "dependencies", but also includes +recommendations+, +suggestions+,
# +replacements+, +provisions+, and +build-dependencies+, as well
# as a few other fields that set a package apart.
#------------------------------------------------------------------------
# What other packages *must* this package have in order to function.
attr_accessor :dependency, :dependencies do
@dependency || []
end
# What other packages *should* be used with this package.
attr_accessor :recommend, :recommends, :recommendations do
@recommend || []
end
# What other packages *could* be useful with this package.
attr_accessor :suggest, :suggests, :suggestions do
@suggest || []
end
# What other packages does this package conflict.
attr_accessor :conflict, :conflicts do
@conflict || []
end
# What other packages does this package replace.
attr_accessor :replace, :replaces, :replacements do
@replace || []
end
# What other package(s) does this package provide the same dependency fulfilment.
# For example, a package 'bar-plus' might fulfill the same dependency criteria
# as package 'bar', so 'bar-plus' is said to provide 'bar'.
attr_accessor :provide, :provides, :provisions do
@provide || []
end
# Abirtary information about what might be needed to use this package.
# This is strictly information for the end-user to consider.
# Eg. "Fast graphics card"
attr_accessor :requirement, :requirements do
@requirement || []
end
# What packages does this package need to build? (eg. 'rake', 'ratch', etc.)
attr_accessor :build_dependency, :build_dependencies do
@build_dependency || []
end
# Abirtary information about what might be needed to build this package.
attr_accessor :build_requirement, :build_requirements do
@build_requirement || []
end
# Packaging
#------------------------------------------------------------------------
# Platform. The default is nil, which is considered cross-platform.
# This tends to only change for special builds.
#
# TODO: if current?
attr_accessor :platform
# Architecture(s) this package can be run on: any, i386, i686, ppc, etc.
# This is strictly informational and is inteded to indicate the possiblities,
# not the particular platform this package runs on.
#attr_accessor :arch, :architecture do
# @arch || "any"
#end
# Script to run prior to build. No entry indicates no compilation.
attr_accessor :compile
# Packages that are intended to compile on install may need this. It is a list
# of "extension scripts" which generate Makefiles for use in compilation.
attr_accessor :extensions do
[@extensions || Dir.glob(File.join(location, 'ext/**/extconf.rb'))].flatten.compact
end
#
#validate "compile script not found" do
# compile ? File.file?(compile) : true
#end
# Package name. This defaults to name, but is here b/c it may
# vary under different packagings (deb vs. gem).
attr_accessor :package do
@package || (
name if respond_to?(:name)
)
end
# Generate documentation on installation?
attr_accessor :document
# Distribution
#------------------------------------------------------------------------
# Files to be distributed in a package. Defaults to all files.
# If an entry is a directory then all it's contents are also included.
# This along with @exclude@ and @ignore@ is used to generate a manifest.
attr_accessor :distribute, :include do
[@distribute || '**/*'].flatten.compact
end
# File to exclude from package. This is usually more useful than
# @distribute@, as it allows you to remove from all files, rather then
# explicitly designate everything to be included. Exlcusions have priority
# over dsitribute's inclusions. If an entry is a directory then all
# it's contents are also excluded.
attr_accessor :exclude do
[@exclude].flatten.compact
end
# Files to generally ignore, mainly used for manifest collection. Ignore
# has priority over @exclude@ and @distribute@.
attr_accessor :ignore do
@ignore || %w{ **/.svn _darcs .config .installed }
end
# Add version tiers to package? If true a package's lib/ and ext/ files
# will be wrapped in a version folder. (This is specialized transfer rule.)
#attr_accessor :tier
# Manifest file.
#def manifest
# @manifest #||= Manifest.open
#end
# Set manifest file, which will load it.
#def manifest=(file)
# @manifest = file
# @filelist = File.read_list(file) #Manifest.open(file)
# return file
#end
# List of file included in a package. This is generated using
# @distribute@, @exlude@ and @ignore@.
def filelist
@filelist ||= collect_files(true)
end
# Validate that the files in the manifest actually exist.
#def validate_manifest
# missing = []
# filelist.each do |f|
# missing << f unless File.exist?(f)
# end
# unless missing.empty?
# raise ValidationError, "manifest lists non-existent files -- " + missing.join(" ")
# end
#end
private
# Collect distribution files.
def collect_files(with_dirs=false)
files = FileList.new
Dir.chdir(location) do
files.include(*distribute)
files.exclude(*exclude)
files.exclude(*ignore)
end
files = files.to_a
# TODO: is there a way to do this with FileList?
unless with_dirs
files = files.select{ |f| !File.directory?(f) }
end
return files
end
# Validation
#------------------------------------------------------------------------
public
#
validate "location is required" do
location
end
#
validate "executables do not exist" do
exes = []
dir = File.join(location, 'bin')
if File.directory?(dir)
Dir.chdir(dir) do
exes = Dir.glob('*')
end
end
(executables - exes).empty?
end
end
end