addfile ./LICENSE hunk ./LICENSE 1 - +* Copyright (c) , +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. addfile ./Main.hs hunk ./Main.hs 1 +----------------------------------------------------------------------------- +-- | +-- Module : MakeBundle.Main +-- Copyright : (c) 2008 Thomas Davie (Anygma, www.anygma.com) +-- License : BSD +-- +-- Maintainer : tom.davie@gmail.org +-- Stability : provisional +-- Portability : portable +-- +-- Constructs a Mac OS X .app bundle from a particular application binary. +-- Includes options to include icon files, and finder get info strings etc. +-- +----------------------------------------------------------------------------- +module Main where + +import qualified System (getArgs) +import System.Directory +import System.FilePath +import Char (toUpper) +import Data.List (isPrefixOf) + +import MakeBundle (makeBundle,generateBundle,Options(..)) + +-- | Major Version Number +majV :: Integral a => a +majV = 0 +-- | Minor Version Number +minV :: Integral a => a +minV = 2 + +main :: IO () +main = do args <- System.getArgs + pwd <- getCurrentDirectory + let as = parseArgs pwd args + if binary as == "" + then putStr usage + else do checkBinaryFile as + checkIconFile as + checkOutputFile as + makeBundle pwd (generateBundle as) + +-- | Produce an arguments record from a list of string options passed to the +-- program. +parseArgs :: FilePath -> [String] -> Options +parseArgs pwd [] = defaultArgs pwd +parseArgs pwd [(b:bin)] = + (defaultArgs pwd){binary=pwd (b:bin),bundleName=(binName ++ ".app")} + where + binName = (toUpper b) : bin +parseArgs pwd ("-f":xs) = (parseArgs pwd xs){force=True} +parseArgs pwd ("-t":x:xs) = (parseArgs pwd xs){bundleType=x} +parseArgs pwd ("-c":x:xs) = (parseArgs pwd xs){creatorCode=x} +parseArgs pwd ("-ic":x:xs) = (parseArgs pwd xs){icon=Just x} +parseArgs pwd ("-is":x:xs) = (parseArgs pwd xs){getInfoString=x} +parseArgs pwd ("-majv":x:xs) = (parseArgs pwd xs){majorVersion=read x} +parseArgs pwd ("-minv":x:xs) = (parseArgs pwd xs){minorVersion=read x} +parseArgs pwd ("-rev":x:xs) = (parseArgs pwd xs){revisionVersion=read x} +parseArgs pwd ("-longv":x:xs) = (parseArgs pwd xs){extraVersionString=x} +parseArgs pwd ("-build":x:xs) = + (parseArgs pwd xs){buildNumber=Just (read x)} +parseArgs pwd ("-id":x:xs) = (parseArgs pwd xs){bundleIdentifier=x} +parseArgs pwd ("-r":xs) = + opts{resources=resources opts ++ res} + where + (res,opts) = collectArgumentList pwd xs +parseArgs pwd ("-fw":xs) = + opts{frameworks=frameworks opts ++ fws} + where + (fws,opts) = collectArgumentList pwd xs +-- Ignore unknown agruments +parseArgs pwd (x:xs) = parseArgs pwd xs + +-- | Default argument set +defaultArgs :: FilePath -> Options +defaultArgs pwd = (Opts {binary="", icon=Nothing, getInfoString="" + ,majorVersion=0,minorVersion=0,revisionVersion=0 + ,extraVersionString = "",buildNumber = Nothing + ,bundleName="",bundleType="APPL",creatorCode="????" + ,bundlePath=pwd,bundleIdentifier="com.none.none" + ,force=False,resources=[],frameworks=[]}) + +{- | Collect a list of arguments bound to the same flag. + This collects all arguments that do not start with a '-' character + Examples: + collectArgumentList ["jam", "ham", "-id", "com.doom.doom"] + -- collects "jam" and "ham" + collectArgumentList [] -- collects no arguments at all + collectArgumentList ["myBinary"] -- collects nothing at all +-} +collectArgumentList :: FilePath -> [String] -> ([String], Options) +collectArgumentList pwd [] = ([],parseArgs pwd []) +collectArgumentList pwd [x] = ([],parseArgs pwd [x]) +collectArgumentList pwd (x:xs) + | "-" `isPrefixOf` x = ([],parseArgs pwd (x:xs)) + | otherwise = let (is,opts) = collectArgumentList pwd xs + in (x:is,opts) + +-- |Generate a usage string +usage :: String +usage = unlines + ["Make Bundle Version " ++ (show majV) ++ "." ++ (show minV) + ,"Usage: mkbndl options binary" + ,"Available options:" + ," -f Force creation of the bundle, even when overwriting files." + ," -t Specify a 4 character bundle-type code, defaults to APPL." + ," -c Specify a 4 character creator code, defaults to ????." + ," -ic Specify an icon file for the bundle." + ," -is Specify a get info string" + ," -majv Specify the major version number of the application." + ," -minv Specify the minor version number of the application." + ," -rev Specify the revision number of the application." + ," -longv Specify a long version string of the application." + ," -build Specify a build number of the application" + ," -id Specify a bundle identifier for the application" + ," -r Specify a list of resources to copy into the bundle's Resources" + ," folder." + ," -fw Specify a list of frameworks to copy into the bundle's" + ," Frameworks folder." + ,"" + ,"Example:" + ," mkbndl -f -majv 3 -minv 1 -rev 1 -longv \"alpha 1\" -build 2697 \\" + ," -id com.anygma.mkbndl -r images icons/* -f Mk.framework mkbndl" + ," Creates a bundle called Mkbndl.app: the Resources folder will contain" + ," images, and the contents of icons; the Frameworks folder will contain" + ," Mk.framework; the MacOS folder will contain mkbndl. The application" + ," will identify itself as com.anygma.mkbndl, version 3.1.1 alpha 1 (2697)"] + +-- |Checks if a given input file exists, and generates an appropriate error +-- message if it is missing or a directory. +checkInputFile :: FilePath -> IO () +checkInputFile fp = + do fe <- doesFileExist fp + de <- doesDirectoryExist fp + if fe then return () + else if de then fail (fp ++ " is a directory.") + else fail (fp ++ " does not exist.") + +-- |Checks if the binary exists. +checkBinaryFile :: Options -> IO () +checkBinaryFile as = checkInputFile (binary as) + +-- |Checks if the icon file exists. +checkIconFile :: Options -> IO () +checkIconFile as = + case icon as of + Nothing -> return () + Just ic -> checkInputFile ic + +-- |Checks if we are going to overwrite the output directory, and if -f has +-- been specified to allow us to do so. +checkOutputFile :: Options -> IO () +checkOutputFile as = + do fe <- doesFileExist (bundleName as) + de <- doesDirectoryExist (bundleName as) + if (fe || de) && not (force as) + then fail (bundleName as ++ " already exists. Use -f to force removal.") + else if fe then removeFile (bundleName as) + else if de then removeDirectoryRecursive (bundleName as) + else return () addfile ./MakeBundle.hs hunk ./MakeBundle.hs 1 +----------------------------------------------------------------------------- +-- | +-- Module : MakeBundle.MakeBundle +-- Copyright : (c) 2008 Thomas Davie (Anygma, www.anygma.com) +-- License : BSD +-- +-- Maintainer : tom.davie@gmail.org +-- Stability : provisional +-- Portability : portable +-- +-- Core functionality of MakeBundle. +-- +----------------------------------------------------------------------------- +module MakeBundle (makeBundle,generateBundle,Options(..)) where + +import System.Directory +import System.FilePath + +-- | A record of the options required for creating the .app bundle. +data Options = Opts {binary :: String, icon :: Maybe String + ,getInfoString :: String, majorVersion :: Int + ,minorVersion :: Int, revisionVersion :: Int + ,extraVersionString :: String, buildNumber :: Maybe Int + ,bundleName :: String,bundleType :: String + ,creatorCode :: String,bundlePath :: FilePath + ,bundleIdentifier :: String,force :: Bool + ,resources :: [String],frameworks :: [String]} + +-- | Specifies the directory structure that MakeBundle should create. +data BundleSpec = Folder FilePath [BundleSpec] + | File FilePath String + | Copy FilePath FilePath + +-- | Constructs a bundle at a file path, given a specification to follow. +-- Will error out if the directories already exist. +makeBundle :: FilePath -> BundleSpec -> IO () +makeBundle pwd (Folder name contents) = + do createDirectory (pwd name) + mapM (makeBundle (pwd name)) contents + return () +makeBundle pwd (File name contents) = + writeFile (pwd name) contents +makeBundle pwd (Copy src dst) = + recursiveCopyDir src (pwd dst) + +recursiveCopyDir :: FilePath -> FilePath -> IO () +recursiveCopyDir src dst = + do f <- doesFileExist src + if f + then copyFile src dst + else do createDirectory dst + dirContents <- getDirectoryContents src + mapM_ (\x -> recursiveCopyDir (src x) (dst x)) + (map takeFileName (tail (tail dirContents))) + +-- | Generates the directory structure needed for a standard .app bundle. +generateBundle :: Options -> BundleSpec +generateBundle os = + Folder (bundleName os) + [Folder "Contents" + [Folder "MacOS" + [Copy (binary os) (takeFileName $ binary os)] + ,File "info.plist" (buildInfoPlist os) + ,File "PkgInfo" (bundleType os ++ creatorCode os) + ,Folder "Resources" + ((case (icon os) of + Nothing -> [] + Just ic -> [Copy ic (takeFileName ic)]) + ++ map (\x -> Copy x (takeFileName x)) (resources os)) + ,Folder "Frameworks" + (map (\x -> Copy x (takeFileName x)) (frameworks os))]] + +-- | Generates an info.plist file specifying the contents of the application. +buildInfoPlist :: Options -> String +buildInfoPlist os = + unlines + [infoPlistHeader + ,makeDictEntry "CFBundleExecutable" (takeFileName $ binary os) + ,makeDictEntry "CFBundleGetInfoString" (getInfoString os) + ,makeDictEntry "CFBundleIconFile" (case icon os of + Nothing -> (takeFileName $ binary os) + Just ic -> (takeFileName ic)) + ,makeDictEntry "CFBundleIdentifier" (bundleIdentifier os) + ,makeDictEntry "CFBundleName" (takeFileName $ binary os) + ,makeDictEntry "CFBundlePackageType" (bundleType os) + ,makeDictEntry "CFBundleSignature" (creatorCode os) + ,makeDictEntry "CFBundleShortVersionString" (shortVersionString os) + ,makeDictEntry "CFBundleVersion" (versionString os) + ,infoPlistFooter] + +-- | Standard header for all info.plist files. +infoPlistHeader :: String +infoPlistHeader = + unlines + ["" + ,"" + ,"" + ,"" + ," CFBundleDevelopmentRegion" + ," English" + ," CFBundleInfoDictionaryVersion" + ," 6.0"] + +-- | Standard footer for all info.plist files. +infoPlistFooter :: String +infoPlistFooter = + unlines + ["" + ,""] + +-- | Constructs an info.plist dictionary entry, given a key and a value. +makeDictEntry :: String -> String -> String +makeDictEntry k v = + concat + [" ", k, "\n" + ," ", v, ""] + +-- | Constructs a standard short version string from the major, minor and +-- revision versions. +shortVersionString :: Options -> String +shortVersionString os = + (show $ majorVersion os) ++ "." ++ (show $ minorVersion os) ++ "." ++ (show $ revisionVersion os) + +-- | Constructs a full version string including extra build information. +versionString :: Options -> String +versionString os = + unwords [(shortVersionString os), extraVersionString os, buildNo] + where + buildNo = case buildNumber os of + Nothing -> "" + Just x -> "(" ++ show x ++ ")" addfile ./README hunk ./README 1 - + mkbndl + ====== + +Generates OS X .app bundles from binaries. + +Install: +======== +> runhaskell Setup.lhs configure +> runhaskell Setup.lhs build +> runhaskell Setup.lhs install (as root) + +Documentation: +============== +> runhaskell Setup.lhs haddock --executables + +or + +> mkbndl --help + +Copyright (c) 2008 Thomas Davie addfile ./Setup.lhs hunk ./Setup.lhs 1 - +#! /usr/bin/env runhaskell + +> import Distribution.Simple +> main = defaultMain addfile ./mkbndl.cabal hunk ./mkbndl.cabal 1 +Name: mkbndl +Cabal-Version: >= 1.2 +Version: 0.2.1 +Synopsis: Makes an OS X .app bundle from a binary. +Description: Allows you to easily construct a .app bundle from a binary + complete with icon files and finder version info. +License: BSD +License-file: LICENSE +Author: Thomas Davie +Maintainer: Thomas Davie (tom.davie@gmail.com) +Data-Files: README +Category: Distribution +build-type: Simple + +Executable mkbndl + Build-Depends: base >= 3.0,haskell98 >= 1.0, + filepath >= 1.1,directory >= 1.0 + Main-is: Main.hs + Other-Modules: MakeBundle