# = TITLE: # # Project # # = COPYING: # # Copyright (c) 2007 Tiger Ops # # 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/dir/multiglob' require 'reap/iobject' module Reap class Project # = Project Metadata # # The Project Metadata class stores project information. This information includes # the general information about a project, such as title, description, homepage, etc. # which is essentially static. Once set, it will probably will never change. # The class also contains default settings for packaging; information that is # usually static, but may vary for a partciular package platform or format. # # When utilizing this class it is important not confuse oneself thinking that a # project is not a project just becuase it is a sub-project. A sub-project is a # project, it just happens to belong to a master project. class Metadata < InfoObject PROJECT_FILE = '{,meta/}{project}{info,}{.yaml,.yml,}' VERSION_FILE = '{,meta/}{version}{.text,.txt,}' # def self.read(location) metadata = read_project(location) versdata = read_version(location) data = {} data.update(metadata) data.update(versdata) new(location, data) end # Parse release file for release information. def self.read_project(location) glob = File.join(location, PROJECT_FILE) file = Dir.glob(glob, File::FNM_CASEFOLD).first if file YAML::load(File.open(file)) else raise LoadError, "project file not found" end end # Parse version file for current release information. def self.read_version(location) glob = File.join(location, VERSION_FILE) file = Dir.glob(glob, File::FNM_CASEFOLD).first if file str = File.read(file) version, status, date, *null = *str.strip.split(/\s+/) date = Date.parse(date).strftime("%Y-%m-%d") data = {'version' => version, 'status' => status, 'date' => date} else data = {} end return data 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, :synopsis # "Unix" name of this project. attr_accessor :project, :name # Overrides Project#name. # TODO: Fit release name into name or package_name (?) #def name # @name ||= release.name #end # If this is a sub-project, then +master+ is the # "Unix" name of the master project to which this # sub-project belongs. attr_accessor :master # 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 do if md = /<(.*?)>/.match(contact) md[1] else "ruby-talk@ruby-lang.org" end end # Official domain associated with this package. attr_accessor :domain # Project's homepage. attr_accessor :homepage, :website # Project's development site. attr_accessor :development, :devsite # Internet address(es) to online documentation. 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 # Status of this release: alpha, beta, RC1, etc. attr_accessor :status do @status || 'alpha' end # Date of release. attr_accessor :date, :released do @date 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, :binpath, :binpaths # Location(s) of libraries (used by Rolls). # In most cases this is something like: # # 'lib/myapp' # # It would be nice if this could just be lib/ as it would mean one less # layer in a project heirarchy. But RubyGems and traditional installers # could not handle this, so this isn't a reasonable course at this point. attr_accessor :lib_path, :lib_paths, :libpath, :libpaths do [@lib_path || "lib/#{name}"].flatten end # The traditional load path(s) (used by Ruby's own site loading and RubyGems). # The default is lib/, which is usually correct. attr_accessor :load_path, :load_paths, :loadpath, :loadpaths, :gem_path, :gem_paths, :gempath, :gempaths do [@load_path || "lib"].flatten end # This only applys to Rolls. It is the default file to load. # TODO: Think of a more descirptive name than 'default'. attr_accessor :default # 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', 'reap', 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 #------------------------------------------------------------------------ # Package name. This defaults to project name, but it may vary under # different package formats --deb vs. gem, for instance. attr_accessor :package do @package || name #@package || ( # name if respond_to?(:name) #) end # 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 # Generate documentation on installation? attr_accessor :document, :has_rdoc # #attr_accessor :package_directory, :package_store do # @package_directory || 'pkg' #end # Package name is generally in the form of +name-version+, or # +name-version-platform+ if +platform+ is specified. # # TODO: Improve buildno support. def package_name if buildno buildno = Time.now.strftime("%H*60+%M") versnum = "#{version}.#{buildno}" else versnum = version end if platform "#{package}-#{versnum}-#{platform}" else "#{package}-#{versnum}" end end alias_method :stage_name, :package_name # 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 # 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 = [] Dir.chdir(location) do files += Dir.multiglob_r(*distribute) files -= Dir.multiglob_r(*exclude) files -= Dir.multiglob_r(*ignore) files -= Dir.multiglob_r('pkg') #package_directory end # Do not include symlinks. files.reject!{ |f| FileTest.symlink?(f) } unless with_dirs files = files.select{ |f| !File.directory?(f) } end return files end # Validation #------------------------------------------------------------------------ public # validate "version is required" do version end # 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 end