kit (empty) → 0.2
raw patch · 17 files changed
+1014/−0 lines, 17 filesdep +HTTPdep +MissingHdep +QuickChecksetup-changed
Dependencies added: HTTP, MissingH, QuickCheck, base, bytestring, cmdargs, containers, directory, filepath, json, mtl, network, process
Files
- Kit/JSON.hs +31/−0
- Kit/Kit.hs +13/−0
- Kit/Main.hs +126/−0
- Kit/Package.hs +47/−0
- Kit/Project.hs +108/−0
- Kit/Repository.hs +84/−0
- Kit/Spec.hs +18/−0
- Kit/Util.hs +98/−0
- Kit/XCode/Builder.hs +148/−0
- Kit/XCode/Common.hs +45/−0
- Kit/XCode/Prefix.hs +13/−0
- Kit/XCode/ProjectFile.hs +198/−0
- Kit/XCode/XCConfig.hs +56/−0
- LICENSE +1/−0
- Main.hs +10/−0
- Setup.hs +2/−0
- kit.cabal +16/−0
+ Kit/JSON.hs view
@@ -0,0 +1,31 @@+module Kit.JSON where+ import Kit.Kit+ import Kit.Spec+ import Kit.Util+ import Control.Applicative+ import Text.JSON+ + instance JSON Kit where+ showJSON kit = makeObj+ [ ("name", showJSON $ kitName kit)+ , ("version", showJSON $ kitVersion kit)+ ]++ readJSON (JSObject obj) = Kit <$> f "name" <*> f "version"+ where f x = mLookup x jsonObjAssoc >>= readJSON+ jsonObjAssoc = fromJSObject obj+ + instance JSON KitSpec where+ showJSON spec = makeObj+ [ ("name", showJSON $ kitName kit)+ , ("version", showJSON $ kitVersion kit)+ , ("dependencies", showJSON $ specDependencies spec)+ ]+ where kit = specKit spec++ readJSON (JSObject obj) = + let myKit = Kit <$> f "name" <*> f "version"+ myConfig = (KitConfiguration <$> f "dependencies" <*> (f "sourceDir" <|> pure "src"))+ in KitSpec <$> myKit <*> myConfig+ where f x = mLookup x jsonObjAssoc >>= readJSON+ jsonObjAssoc = fromJSObject obj
+ Kit/Kit.hs view
@@ -0,0 +1,13 @@+module Kit.Kit where+ import Kit.Util+ + data Kit = Kit {+ kitName :: String,+ kitVersion :: String+ } deriving (Eq, Show, Ord, Read)++ kitFileName :: Kit -> String+ kitFileName k = kitName k ++ "-" ++ kitVersion k++ kitConfigFile :: Kit -> String+ kitConfigFile kit = kitFileName kit </> (kitName kit ++ ".xcconfig")
+ Kit/Main.hs view
@@ -0,0 +1,126 @@+module Kit.Main where+ import qualified Data.ByteString as BS++ import Control.Monad.Trans+ import Data.List+ import Data.Maybe+ import Data.Monoid+ import Kit.Kit+ import Kit.Package+ import Kit.Project+ import Kit.Repository+ import Kit.Spec+ import Kit.Util + import Kit.XCode.Builder+ import qualified Data.Traversable as T+ import System.Cmd+ import System.Environment+ import Text.JSON++ defaultLocalRepoPath = do+ home <- getHomeDirectory+ return $ home </> ".kit" </> "repository"+ defaultLocalRepository = fmap fileRepo defaultLocalRepoPath+ + handleFails (Left e) = do+ print e+ return ()+ handleFails (Right _) = return ()+ + update :: IO ()+ update = unKitIO g >>= handleFails+ where g = do+ repo <- liftIO defaultLocalRepository+ deps <- getMyDeps repo+ puts $ "Dependencies: " ++ (stringJoin ", " $ map kitFileName deps)+ liftIO $ mapM (installKit repo) deps+ puts " -> Generating XCode project..."+ liftIO $ generateXCodeProject $ deps |> kitFileName+ liftIO $ generateXCodeConfig $ deps |> kitFileName+ puts "Kit complete."+ where p x = liftIO $ print x+ puts x = liftIO $ putStrLn x+ + packageKit :: IO ()+ packageKit = do+ mySpec <- unKitIO myKitSpec+ T.for mySpec package+ return ()+ + deployLocal :: IO ()+ deployLocal = do+ mySpec <- unKitIO myKitSpec+ T.for mySpec package+ T.for mySpec x+ handleFails mySpec+ where+ x :: KitSpec -> IO ()+ x spec = let + k = specKit spec+ kf = kitFileName . specKit $ spec+ pkg = (kf ++ ".tar.gz")+ in do+ repo <- defaultLocalRepoPath+ let thisKitDir = repo </> "kits" </> kitName k </> kitVersion k+ mkdir_p thisKitDir+ copyFile pkg $ thisKitDir </> pkg+ copyFile "KitSpec" $ thisKitDir </> "KitSpec"+ + verify :: Maybe String -> IO ()+ verify sdk = (unKitIO f >>= handleFails)+ where+ f = do+ mySpec <- myKitSpec+ puts "Checking that the kit can be depended upon..."+ puts " #> Deploying locally"+ liftIO deployLocal+ puts " #> Building temporary parent project"+ tmp <- liftIO getTemporaryDirectory+ liftIO $ inDirectory tmp $ do+ let kitVerifyDir = "kit-verify"+ cleanOrCreate kitVerifyDir+ inDirectory kitVerifyDir $ do+ writeFile "KitSpec" $ encode (KitSpec (Kit "verify-kit" "1.0") $ defaultConfiguration{kitConfigDependencies=[specKit mySpec]})+ update+ inDirectory "Kits" $ do+ system $ "xcodebuild -sdk " ++ (fromMaybe "iphonesimulator4.0" sdk)+ putStrLn "OK."+ puts "End checks."+ puts = liftIO . putStrLn+ + createSpec :: String -> String -> IO ()+ createSpec name version = do+ let kit =(Kit name version)+ let spec = KitSpec kit defaultConfiguration+ writeFile "KitSpec" $ encode spec+ putStrLn $ "Created KitSpec for " ++ kitFileName kit+ return ()+ + handleArgs :: [String] -> IO ()+ handleArgs ["update"] = update+ handleArgs ["package"] = packageKit+ handleArgs ["publish-local"] = deployLocal+ handleArgs ["verify"] = verify Nothing+ handleArgs ["verify", sdk] = verify $ Just sdk+ handleArgs ["create-spec", name, version] = createSpec name version+ + handleArgs _ = putStrLn f where+ f = "Usage: kit command [arguments]" `nl`+ "Commands:" `nl`+ "\tupdate\t\t\t\tCreate an Xcode project to wrap the dependencies defined in ./KitSpec" `nl`+ "\tcreate-spec NAME VERSION\tWrite out a KitSpec file using the given name and version." `nl`+ "\tpackage\t\t\t\tCreate a tar.gz wrapping this kit" `nl`+ "\tverify [SDK]\t\t\tPackage this kit, then attempt to use it as a dependency in an empty project." `nl`+ "\tpublish-local\t\t\tPackage this kit, then deploy it to the local repository (~/.kit/repository)" + nl a b = a ++ "\n" ++ b+ + kitMain :: IO ()+ kitMain = do+ localRepo <- defaultLocalRepository+ path <- defaultLocalRepoPath+ mkdir_p path+ args <- getArgs+ handleArgs args++ +
+ Kit/Package.hs view
@@ -0,0 +1,47 @@+module Kit.Package (package) where++ import Kit.Kit+ import Kit.Spec+ import Kit.Util+ import Kit.Project+ import System.Cmd+ import Control.Monad.Trans+ import Control.Monad+ import Data.List+ import Data.Maybe + import Debug.Trace+ + {-+ Package format+ src/ | configurable source dir+ test/+ KitSpec+ *.codeproj+ -}+ + fileBelongsInPackage :: KitConfiguration -> FilePath -> Bool+ fileBelongsInPackage config fp = let+ a = elem fp [sourceDir config, "test", "KitSpec"]+ b = "xcodeproj" `isSuffixOf` fp+ c = "xcconfig" `isSuffixOf` fp+ d = ".pch" `isSuffixOf` fp+ in a || b || c || d+ + package :: KitSpec -> IO ()+ package spec = do+ tempDir <- getTemporaryDirectory+ current <- getCurrentDirectory+ let kd = tempDir </> kitPath+ cleanOrCreate kd+ contents <- getDirectoryContents "."+ mapM_ (cp_r_to kd) (filter (fileBelongsInPackage . specConfiguration $ spec) contents)+ inDirectory tempDir $ sh $ "tar czf " ++ (current </> (kitPath ++ ".tar.gz")) ++ " " ++ kitPath+ return ()+ where+ kitPath = kitFileName . specKit $ spec+ sh c = liftIO $ system (trace c c)+ puts c = liftIO $ putStrLn c+ p c = liftIO $ print c+ cp_r_to kd c = sh $ "cp -r " ++ c ++ " " ++ kd ++ "/"+ +
+ Kit/Project.hs view
@@ -0,0 +1,108 @@+module Kit.Project (+ getDeps,+ getMyDeps,+ installKit,+ generateXCodeConfig,+ generateXCodeProject,+ myKitSpec)+ where+ + import Control.Monad.Trans+ import Control.Monad+ import Control.Applicative+ import Data.Foldable+ import Data.Maybe+ import Data.List+ import Data.Monoid+ import System.Cmd+ import Kit.Kit+ import Kit.Repository+ import Kit.Util+ import Kit.Spec+ import Kit.XCode.Builder+ import Kit.XCode.XCConfig+ import Kit.XCode.Prefix+ import Text.JSON+ import Kit.JSON+ import qualified Data.Traversable as T+ + kitDir = "." </> "Kits"+ projectDir = "KitDeps.xcodeproj"+ prefixFile = "KitDeps_Prefix.pch"+ projectFile = projectDir </> "project.pbxproj"+ prefixDefault = "#ifdef __OBJC__\n" ++ " #import <Foundation/Foundation.h>\n #import <UIKit/UIKit.h>\n" ++ "#endif\n"+ + generateXCodeProject :: [FilePath] -> IO ()+ generateXCodeProject kitFileNames = do+ mkdir_p kitDir+ inDirectory kitDir $ do+ let find tpe inDir = glob (inDir ++ "/src/**/*" ++ tpe)+ let find' tpe = fmap join . T.for kitFileNames $ find tpe+ headers <- find' ".h"+ sources <- (\a b c -> a ++ b ++ c) <$> glob "**/src/**/*.m" <*> glob "**/src/**/*.mm" <*> glob "**/src/**/*.c"+ mkdir_p projectDir+ writeFile projectFile $ buildXCodeProject headers sources+ combinedHeader <- generatePrefixHeader kitFileNames+ writeFile prefixFile $ prefixDefault ++ combinedHeader ++ "\n"++ readConfig' :: Kit -> IO (Maybe XCConfig)+ readConfig' kit = do+ exists <- doesFileExist fp+ contents <- T.sequence (fmap readFile $ justTrue exists fp)+ return $ fmap (fileContentsToXCC $ kitName kit) contents+ where+ fp = kitConfigFile kit++ generateXCodeConfig :: [FilePath] -> IO ()+ generateXCodeConfig kitFileNames = do+ inDirectory kitDir $ unKitIO $ do+ ca <- readMany kitFileNames "KitSpec" (\x -> readSpec x |> specKit >>= (liftIO . readConfig'))+ let combinedConfig = multiConfig "KitConfig" ca+ let contents = configToString combinedConfig+ let xcconfig = "HEADER_SEARCH_PATHS = $(HEADER_SEARCH_PATHS) " ++ (stringJoin " " $ kitFileNames >>= (\x -> [kitDir </> x </> "src", x </> "src"])) ++ "\n" ++ "GCC_PRECOMPILE_PREFIX_HEADER = YES\nGCC_PREFIX_HEADER = $(SRCROOT)/KitDeps_Prefix.pch\n"+ liftIO $ writeFile "Kit.xcconfig" $ xcconfig ++ "\n" ++ contents+ return ()+ + depsForSpec :: KitRepository -> KitSpec -> KitIO [Kit]+ depsForSpec kr spec = do+ deps <- mapM (getDeps kr) (specDependencies spec)+ return $ specDependencies spec ++ join deps + + getDeps :: KitRepository -> Kit -> KitIO [Kit]+ getDeps kr kit = getKitSpec kr kit >>= depsForSpec kr++ getMyDeps :: KitRepository -> KitIO [Kit]+ getMyDeps kr = myKitSpec >>= depsForSpec kr++ installKit :: KitRepository -> Kit -> IO ()+ installKit kr kit = do+ tmpDir <- getTemporaryDirectory+ let fp = tmpDir </> (kitFileName kit ++ ".tar.gz")+ putStrLn $ " -> Installing " ++ kitFileName kit+ fmap fromJust $ getKit kr kit fp+ let dest = kitDir+ mkdir_p dest+ inDirectory dest $ sh ("tar zxf " ++ fp)+ return ()+ where sh = system++ readSpec :: FilePath -> KitIO KitSpec+ readSpec kitSpecPath = readSpecContents kitSpecPath >>= KitIO . return . parses+ + myKitSpec :: KitIO KitSpec+ myKitSpec = readSpec "KitSpec"++ -- private!+ checkExists :: FilePath -> KitIO FilePath+ checkExists kitSpecPath = do+ doesExist <- liftIO $ doesFileExist kitSpecPath+ maybeToKitIO ("Couldn't find the spec at " ++ kitSpecPath) (justTrue doesExist kitSpecPath)+ + readSpecContents :: FilePath -> KitIO String+ readSpecContents kitSpecPath = checkExists kitSpecPath >>= liftIO . readFile+ + parses :: String -> Either [KitError] KitSpec+ parses contents = case (decode contents) of + Ok a -> Right a+ Error a -> Left [a]+
+ Kit/Repository.hs view
@@ -0,0 +1,84 @@+{-++ Exposes the repositories.+ Allows you to:+ * copy down kit packages (getKit)+ * read kit specs (getKitSpec)+-}++module Kit.Repository (+ getKit,+ getKitSpec,+ webRepo,+ fileRepo,+ KitRepository+ ) where+ + import Kit.Spec+ import Kit.Kit+ import Kit.Util+ import Data.List+ import Network.HTTP+ import Network.URI+ import Network.BufferType+ import Data.Maybe+ import Data.Traversable+ import Control.Monad+ import Control.Monad.Trans+ import Debug.Trace+ import Text.JSON+ import Kit.JSON+ import qualified Data.ByteString as BS++ data KitRepository = KitRepository {+ repoSave :: String -> FilePath -> IO (Maybe ()),+ repoRead :: String -> IO (Maybe String)+ }+ + getKit :: KitRepository -> Kit -> FilePath -> IO (Maybe ())+ getKit kr k = repoSave kr (kitPackagePath k)+ + getKitSpec :: KitRepository -> Kit -> KitIO KitSpec+ getKitSpec kr k = do+ mbKitStuff <- liftIO $ repoRead kr (kitSpecPath k)+ maybe (kitError $ "Missing " ++ kitFileName k) f mbKitStuff+ where f contents = maybeToKitIO ("Invalid KitSpec file for " ++ kitFileName k) $ case (decode contents) of+ Ok a -> Just a+ Error _ -> Nothing+ + webRepo :: String -> KitRepository+ webRepo baseUrl = KitRepository save read where+ save = download + read = getBody++ fileRepo :: String -> KitRepository+ fileRepo baseDir = KitRepository save read where+ save src destPath = Just <$> copyFile (baseDir </> src) destPath+ read path = let file = (baseDir </> path) in do+ exists <- doesFileExist file+ sequenceA $ justTrue exists $ readFile file+ + -- private!+ baseKitPath :: Kit -> String+ baseKitPath k = joinS ["kits", kitName k, kitVersion k] "/"+ where joinS xs x = foldl1 (++) $ intersperse x xs + + kitPackagePath k = baseKitPath k ++ "/" ++ kitFileName k ++ ".tar.gz"+ kitSpecPath k = baseKitPath k ++ "/" ++ "KitSpec"+ + getBody :: BufferType a => HStream a => String -> IO (Maybe a)+ getBody path = let+ request = defaultGETRequest_ . fromJust . parseURI+ checkResponse r = justTrue (rspCode r == (2,0,0)) r+ leftMaybe = either (const Nothing) Just + in do+ rr <- Network.HTTP.simpleHTTP $ request path+ return $ fmap rspBody $ leftMaybe rr+ + download :: String -> FilePath -> IO (Maybe ())+ download url destination = do+ body <- getBody url+ sequenceA $ fmap (BS.writeFile destination) body+ + +
+ Kit/Spec.hs view
@@ -0,0 +1,18 @@+module Kit.Spec where+ import Kit.Kit+ + data KitSpec = KitSpec {+ specKit :: Kit,+ specConfiguration :: KitConfiguration+ } deriving (Show, Read)+ + data KitConfiguration = KitConfiguration {+ kitConfigDependencies :: [Kit],+ sourceDir :: FilePath+ } deriving (Show, Read)+ + specDependencies :: KitSpec -> [Kit]+ specDependencies = kitConfigDependencies . specConfiguration+ + defaultConfiguration :: KitConfiguration+ defaultConfiguration = KitConfiguration [] "src"
+ Kit/Util.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE TypeSynonymInstances #-}++module Kit.Util(+ module Kit.Util, + module Control.Applicative, + module System.FilePath.Posix,+ module System.Directory+ ) where+ import System.FilePath.Posix+ import System.Process+ import System.Directory+ import Data.Maybe+ import Data.List+ import qualified Data.Traversable as T+ import Data.Foldable+ import Control.Applicative+ import Control.Monad+ import Data.Monoid+ import Control.Monad.Trans+ + (|>) ma f = fmap f ma+ + maybeRead :: Read a => String -> Maybe a+ maybeRead = fmap fst . listToMaybe . reads+ + justTrue :: Bool -> a -> Maybe a+ justTrue True a = Just a+ justTrue False _ = Nothing+ + maybeToRight :: b -> Maybe a -> Either b a+ maybeToRight _ (Just a) = Right a+ maybeToRight b Nothing = Left b+ + maybeToLeft :: b -> Maybe a -> Either a b+ maybeToLeft _ (Just a) = Left a+ maybeToLeft b Nothing = Right b+ + instance Foldable (Either c) where+ foldr f z (Left c) = z+ foldr f z (Right a) = f a z+ + instance T.Traversable (Either c) where+ sequenceA = either (pure . Left) (Right <$>)+ + type KitError = String+ + data KitIO a = KitIO { unKitIO :: (IO (Either [KitError] a)) }+ + kitError :: KitError -> KitIO a+ kitError e = KitIO . return $ Left [e]+ + maybeToKitIO msg = maybe (kitError msg) return+ + instance Monad KitIO where+ (KitIO ioE) >>= f = KitIO $ ioE >>= g+ where g l@(Left v) = return (Left v)+ g (Right v) = unKitIO $ f v+ + return = KitIO . return . Right + + instance Functor KitIO where+ fmap f kio = kio >>= (return . f)+ + instance MonadIO KitIO where+ liftIO v = KitIO (fmap Right v)++ mLookup a as = maybe (fail $ "No such element: " ++ a) return (lookup a as) + + mkdir_p :: FilePath -> IO ()+ mkdir_p = createDirectoryIfMissing True+ + cleanOrCreate :: FilePath -> IO ()+ cleanOrCreate directory = do+ exists <- doesDirectoryExist directory+ when exists $ removeDirectoryRecursive directory+ mkdir_p directory+ + inDirectory :: FilePath -> IO a -> IO a+ inDirectory dir actions = do+ cwd <- getCurrentDirectory+ setCurrentDirectory dir+ v <- actions+ setCurrentDirectory cwd+ return v+ + glob :: String -> IO [String]+ glob pattern = fmap lines (readProcess "ruby" ["-e", "puts Dir.glob(\"" ++ pattern ++ "\")"] [])+ + stringJoin :: Monoid a => a -> [a] -> a+ stringJoin x = mconcat . intersperse x+ + -- For each directory that exists, execute the function with the given filepath+ readMany :: MonadIO m => [FilePath] -> FilePath -> (String -> m (Maybe a)) -> m [a]+ readMany dirs fileInDir f = do+ directories <- liftIO $ filterM doesDirectoryExist dirs+ let kitSpecFiles = map (</> fileInDir) directories+ kitContents <- mapM f kitSpecFiles+ return $ catMaybes kitContents
+ Kit/XCode/Builder.hs view
@@ -0,0 +1,148 @@++module Kit.XCode.Builder (buildXCodeProject) where++ import Data.Monoid+ import Data.List+ import Kit.XCode.Common+ import Kit.XCode.ProjectFile+ import Kit.Util+ + createBuildFile :: Integer -> FilePath -> PBXBuildFile+ createBuildFile i path = PBXBuildFile uuid1 $ PBXFileReference uuid2 path+ where uuid1 = uuid i+ uuid2 = uuid $ i + 10000000+ + buildXCodeProject :: [FilePath] -> [FilePath] -> String+ buildXCodeProject headers sources = + projectPbxProj bfs frs classes hs srcs + where+ sourceStart = toInteger (length headers + 1)+ headerBuildFiles = zipWith createBuildFile [1..] headers+ sourceBuildFiles = zipWith createBuildFile [sourceStart..] sources+ allBuildFiles = (sourceBuildFiles ++ headerBuildFiles)+ bfs = buildFileSection allBuildFiles+ frs = fileReferenceSection $ map buildFileReference allBuildFiles+ classes = classesSection $ map buildFileReference (sourceBuildFiles ++ headerBuildFiles)+ hs = headersSection headerBuildFiles+ srcs = sourcesSection sourceBuildFiles+ + xxx :: FilePath -> UUID -> UUID -> PBXBuildFile+ xxx fp buildId fileId = PBXBuildFile buildId (PBXFileReference fileId fp)++ -- 47021EC1117A7776003DB5B7 /* motive.m in Sources */ = {isa = PBXBuildFile; fileRef = 47021EBF117A7776003DB5B7 /* motive.m */; };+ buildFileItem :: PBXBuildFile -> String+ buildFileItem bf = lineItem i comment dict+ where fr = buildFileReference bf+ i = buildFileId bf+ comment = let ft Unknown = "Unknown"+ ft Header = "Headers"+ ft Source = "Sources"+ bit = ft . fileType . fileReferencePath $ fr+ in fileReferenceName fr ++ " in " ++ bit+ dict = [+ "isa" ~> "PBXBuildFile",+ "fileRef" ~> (fileReferenceId fr ++ " /* " ++ fileReferenceName fr ++ " */")+ ]++ fileTypeBit :: FileType -> String+ fileTypeBit Header = "sourcecode.c.h"+ fileTypeBit Source = "sourcecode.c.objc"+ fileTypeBit Unknown = "sourcecode.unknown"++ -- 47021EBE117A7776003DB5B7 /* motive.h */ = {isa = PBXFileReference; fileEncoding = 4;+ -- lastKnownFileType = sourcecode.c.h; name = motive.h; path = "kits/motive-0.1/src/motive.h"; sourceTree = "<group>"; };+ fileReferenceItem :: PBXFileReference -> String+ fileReferenceItem fr = lineItem fid fileName dict+ where+ fid = fileReferenceId fr+ path = fileReferencePath fr+ fileName = fileReferenceName fr+ dict = [+ "isa" ~> "PBXFileReference",+ "fileEncoding" ~> "4",+ "lastKnownFileType" ~> (fileTypeBit . fileType $ fileName),+ "name" ~> show fileName,+ "path" ~> show path,+ "sourceTree" ~> show "<group>"+ ]+ + layoutSection :: String -> [String] -> String+ layoutSection name body = let + front = "/* Begin " ++ name ++ " section */"+ middle = map (" " ++) body+ back = "/* End " ++ name ++ " section */"+ in mconcat $ intersperse "\n" ((front : middle) ++ [back])++ buildFileSection :: [PBXBuildFile] -> String+ buildFileSection bfs = layoutSection "PBXBuildFile" (map buildFileItem bfs ++ [+ "4728C530117C02B10027D7D1 /* Kit.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 4728C52F117C02B10027D7D1 /* Kit.xcconfig */; };",+ "AA747D9F0F9514B9006C5449 /* KitDeps_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = AA747D9E0F9514B9006C5449 /* KitDeps_Prefix.pch */; };",+ "AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AACBBE490F95108600F1A2B1 /* Foundation.framework */; };"+ ]) + + fileReferenceSection :: [PBXFileReference] -> String+ fileReferenceSection refs = layoutSection "PBXFileReference" (map fileReferenceItem refs ++ [+ "4728C52F117C02B10027D7D1 /* Kit.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Kit.xcconfig; sourceTree = \"<group>\"; };",+ "AA747D9E0F9514B9006C5449 /* KitDeps_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KitDeps_Prefix.pch; sourceTree = SOURCE_ROOT; };",+ "AACBBE490F95108600F1A2B1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };",+ "D2AAC07E0554694100DB518D /* libKitDeps.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libKitDeps.a; sourceTree = BUILT_PRODUCTS_DIR; };"+ ])++ classesSection :: [PBXFileReference] -> String+ classesSection files = lineItem "08FB77AEFE84172EC02AAC07" "Classes" dict+ where dict = [+ "isa" ~> "PBXGroup",+ "children" ~> ("(" ++ (mconcat (intersperse "," (map fileReferenceId files))) ++ ",)"),+ "name" ~> "Classes",+ "sourceTree" ~> show "<group>"+ ]+ + {-/* Begin PBXHeadersBuildPhase section */+ D2AAC07A0554694100DB518D /* Headers */ = {+ isa = PBXHeadersBuildPhase;+ buildActionMask = 2147483647;+ files = (+ AA747D9F0F9514B9006C5449 /* KitDeps_Prefix.pch in Headers */,+ 4791C244117A7893001EB278 /* motive.h in Headers */,+ );+ runOnlyForDeploymentPostprocessing = 0;+ };+ /* End PBXHeadersBuildPhase section */-} + headersSection :: [PBXBuildFile] -> String+ headersSection bfs = layoutSection "PBXHeadersBuildPhase" [lineItem "D2AAC07A0554694100DB518D" "Headers" [+ "isa" ~> "PBXHeadersBuildPhase",+ "buildActionMask" ~> "2147483647",+ "files" ~> ("(" ++ (stringJoin "," $ prefixPchUUID : map buildFileId bfs) ++ ",)"),+ "runOnlyForDeploymentPostprocessing" ~> "0"+ ]]+ where prefixPchUUID = "AA747D9F0F9514B9006C5449"+ + {-+ /* Begin PBXSourcesBuildPhase section */+ D2AAC07B0554694100DB518D /* Sources */ = {+ isa = PBXSourcesBuildPhase;+ buildActionMask = 2147483647;+ files = (+ 47021EC1117A7776003DB5B7 /* motive.m in Sources */,+ );+ runOnlyForDeploymentPostprocessing = 0;+ };+ /* End PBXSourcesBuildPhase section */+ -}+ sourcesSection :: [PBXBuildFile] -> String+ sourcesSection bfs = layoutSection "PBXSourcesBuildPhase" [lineItem "D2AAC07B0554694100DB518D" "Sources" [+ "isa" ~> "PBXSourcesBuildPhase",+ "buildActionMask" ~> "2147483647",+ "files" ~> ("(" ++ (stringJoin "," $ map buildFileId bfs) ++ ",)"),+ "runOnlyForDeploymentPostprocessing" ~> "0"+ ]]+ + testBuilder = let+ header = PBXFileReference "1" "fk/fk.h"+ source = PBXFileReference "2" "fk/fk.m"+ headerBF = PBXBuildFile "10" header+ sourceBF = PBXBuildFile "20" source+ in do+ putStrLn . buildFileSection $ [headerBF, sourceBF]+ putStrLn . fileReferenceSection $ [header, source]+
+ Kit/XCode/Common.hs view
@@ -0,0 +1,45 @@+module Kit.XCode.Common where+ import Data.Monoid+ import Data.List+ import System.FilePath.Posix+ + type UUID = String+ type Dict = [(String, String)]+ + data FileType = Header | Source | Unknown+ + fileType :: String -> FileType+ fileType x | ".h" `isSuffixOf` x = Header+ fileType x | ".m" `isSuffixOf` x = Source+ fileType x | ".mm" `isSuffixOf` x = Source+ fileType x | ".c" `isSuffixOf` x = Source+ fileType _ = Unknown+ + (~>) = (,)+ + data PBXBuildFile = PBXBuildFile {+ buildFileId :: String,+ buildFileReference :: PBXFileReference+ }+ + data PBXFileReference = PBXFileReference {+ fileReferenceId :: String,+ fileReferencePath :: String+ }+ + fileReferenceName = takeFileName . fileReferencePath+ + lineItem :: UUID -> String -> Dict -> String+ lineItem uuid comment dict = uuid ++ " /* " ++ comment ++ " */ = " ++ buildDict dict ++ ";"+ + buildDict :: Dict -> String+ buildDict ps = g . mconcat . map (\x -> fst x ++ " = " ++ snd x ++ "; ") $ ps+ where g s = "{" ++ s ++ "}"+ + uuid :: Integer -> UUID+ uuid i = let+ s = show i+ lengthS = length s+ pad = 24 - lengthS+ in+ replicate pad '0' ++ s
+ Kit/XCode/Prefix.hs view
@@ -0,0 +1,13 @@+module Kit.XCode.Prefix where+ + import Kit.Util+ import qualified Data.Traversable as T+ + kitPrefixFile = "Prefix.pch"+ + generatePrefixHeader :: [FilePath] -> IO String+ generatePrefixHeader kitFileNames = do+ ca <- readMany kitFileNames kitPrefixFile $ \s -> do+ exists <- doesFileExist s+ T.for (justTrue exists s) readFile+ return $ stringJoin "\n" ca
+ Kit/XCode/ProjectFile.hs view
@@ -0,0 +1,198 @@+{-++ Constant sections of the project file+-}+module Kit.XCode.ProjectFile (projectPbxProj) where+ + nl a b = a ++ "\n" ++ b + + projectPbxProj bfs fileRefsSection classes headers sources = + top `nl` bfs `nl` fileRefsSection `nl` next1 `nl` classes `nl` next2 `nl` headers `nl` next3 `nl` sources `nl` bottom+ + top = "// !$*UTF8*$!" `nl` + "{" `nl`+ " archiveVersion = 1;" `nl`+ " classes = {" `nl`+ " };" `nl`+ " objectVersion = 45;" `nl`+ " objects = {"+ + next1 = "/* Begin PBXFrameworksBuildPhase section */" `nl`+ "D2AAC07C0554694100DB518D /* Frameworks */ = {" `nl`+ "isa = PBXFrameworksBuildPhase;" `nl`+ "buildActionMask = 2147483647;" `nl`+ "files = (" `nl`+ "AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */," `nl`+ ");" `nl`+ "runOnlyForDeploymentPostprocessing = 0;" `nl`+ "};" `nl`+ "/* End PBXFrameworksBuildPhase section */" `nl`+ "" `nl`+ "/* Begin PBXGroup section */" `nl`+ "034768DFFF38A50411DB9C8B /* Products */ = {" `nl`+ "isa = PBXGroup;" `nl`+ "children = (" `nl`+ "D2AAC07E0554694100DB518D /* libKitDeps.a */," `nl`+ ");" `nl`+ "name = Products;" `nl`+ "sourceTree = \"<group>\";" `nl`+ "};" `nl`+ "0867D691FE84028FC02AAC07 /* KitDeps */ = {" `nl`+ "isa = PBXGroup;" `nl`+ "children = (" `nl`+ "08FB77AEFE84172EC02AAC07 /* Classes */," `nl`+ "32C88DFF0371C24200C91783 /* Other Sources */," `nl`+ "0867D69AFE84028FC02AAC07 /* Frameworks */," `nl`+ "034768DFFF38A50411DB9C8B /* Products */," `nl`+ "4728C52F117C02B10027D7D1 /* Kit.xcconfig */," `nl`+ ");" `nl`+ "name = KitDeps;" `nl`+ "sourceTree = \"<group>\";" `nl`+ "};" `nl`+ "0867D69AFE84028FC02AAC07 /* Frameworks */ = {" `nl`+ "isa = PBXGroup;" `nl`+ "children = (" `nl`+ "AACBBE490F95108600F1A2B1 /* Foundation.framework */," `nl`+ ");" `nl`+ "name = Frameworks;" `nl`+ "sourceTree = \"<group>\";" `nl`+ "};"+ + next2 = "32C88DFF0371C24200C91783 /* Other Sources */ = {" `nl`+ "isa = PBXGroup;" `nl`+ "children = (" `nl`+ "AA747D9E0F9514B9006C5449 /* KitDeps_Prefix.pch */," `nl`+ ");" `nl`+ "name = \"Other Sources\";" `nl`+ "sourceTree = \"<group>\";" `nl`+ "};" `nl`+ "/* End PBXGroup section */"+ + next3 = "/* Begin PBXNativeTarget section */" `nl`+ "D2AAC07D0554694100DB518D /* KitDeps */ = {" `nl`+ "isa = PBXNativeTarget;" `nl`+ "buildConfigurationList = 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget \"KitDeps\" */;" `nl`+ "buildPhases = (" `nl`+ "D2AAC07A0554694100DB518D /* Headers */," `nl`+ "D2AAC07B0554694100DB518D /* Sources */," `nl`+ "D2AAC07C0554694100DB518D /* Frameworks */," `nl`+ ");" `nl`+ "buildRules = (" `nl`+ ");" `nl`+ "dependencies = (" `nl`+ ");" `nl`+ "name = KitDeps;" `nl`+ "productName = KitDeps;" `nl`+ "productReference = D2AAC07E0554694100DB518D /* libKitDeps.a */;" `nl`+ "productType = \"com.apple.product-type.library.static\";" `nl`+ "};" `nl`+ "/* End PBXNativeTarget section */" `nl`+ "" `nl`+ "/* Begin PBXProject section */" `nl`+ "0867D690FE84028FC02AAC07 /* Project object */ = {" `nl`+ "isa = PBXProject;" `nl`+ "buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject \"KitDepsCustom\" */;" `nl`+ "compatibilityVersion = \"Xcode 3.1\";" `nl`+ "hasScannedForEncodings = 1;" `nl`+ "mainGroup = 0867D691FE84028FC02AAC07 /* KitDeps */;" `nl`+ "productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;" `nl`+ "projectDirPath = \"\";" `nl`+ "projectRoot = \"\";" `nl`+ "targets = (" `nl`+ "D2AAC07D0554694100DB518D /* KitDeps */," `nl`+ ");" `nl`+ "};" `nl`+ "/* End PBXProject section */"+ + bottom = "/* Begin XCBuildConfiguration section */" `nl`+ "1DEB921F08733DC00010E9CD /* Debug */ = {" `nl`+ "isa = XCBuildConfiguration;" `nl`+ "buildSettings = {" `nl`+ "ALWAYS_SEARCH_USER_PATHS = NO;" `nl`+ "ARCHS = \"$(ARCHS_STANDARD_32_BIT)\";" `nl`+ "COPY_PHASE_STRIP = NO;" `nl`+ "DSTROOT = /tmp/KitDeps.dst;" `nl`+ "GCC_DYNAMIC_NO_PIC = NO;" `nl`+ "GCC_ENABLE_FIX_AND_CONTINUE = YES;" `nl`+ "GCC_MODEL_TUNING = G5;" `nl`+ "GCC_OPTIMIZATION_LEVEL = 0;" `nl`+ "GCC_PRECOMPILE_PREFIX_HEADER = YES;" `nl`+ "GCC_PREFIX_HEADER = KitDeps_Prefix.pch;" `nl`+ "INSTALL_PATH = /usr/local/lib;" `nl`+ "PRODUCT_NAME = KitDeps;" `nl`+ "};" `nl`+ "name = Debug;" `nl`+ "};" `nl`+ "1DEB922008733DC00010E9CD /* Release */ = {" `nl`+ "isa = XCBuildConfiguration;" `nl`+ "buildSettings = {" `nl`+ "ALWAYS_SEARCH_USER_PATHS = NO;" `nl`+ "ARCHS = \"$(ARCHS_STANDARD_32_BIT)\";" `nl`+ "DSTROOT = /tmp/KitDeps.dst;" `nl`+ "GCC_MODEL_TUNING = G5;" `nl`+ "GCC_PRECOMPILE_PREFIX_HEADER = YES;" `nl`+ "GCC_PREFIX_HEADER = KitDeps_Prefix.pch;" `nl`+ "INSTALL_PATH = /usr/local/lib;" `nl`+ "PRODUCT_NAME = KitDeps;" `nl`+ "};" `nl`+ "name = Release;" `nl`+ "};" `nl`+ "1DEB922308733DC00010E9CD /* Debug */ = {" `nl`+ "isa = XCBuildConfiguration;" `nl`+ "baseConfigurationReference = 4728C52F117C02B10027D7D1 /* Kit.xcconfig */;" `nl`+ "buildSettings = {" `nl`+ "ARCHS = \"$(ARCHS_STANDARD_32_BIT)\";" `nl`+ "GCC_C_LANGUAGE_STANDARD = c99;" `nl`+ "GCC_OPTIMIZATION_LEVEL = 0;" `nl`+ "GCC_WARN_ABOUT_RETURN_TYPE = YES;" `nl`+ "GCC_WARN_UNUSED_VARIABLE = YES;" `nl`+ "OTHER_LDFLAGS = \"-ObjC\";" `nl`+ "PREBINDING = NO;" `nl`+ "SDKROOT = iphoneos4.0;" `nl`+ "};" `nl`+ "name = Debug;" `nl`+ "};" `nl`+ "1DEB922408733DC00010E9CD /* Release */ = {" `nl`+ "isa = XCBuildConfiguration;" `nl`+ "baseConfigurationReference = 4728C52F117C02B10027D7D1 /* Kit.xcconfig */;" `nl`+ "buildSettings = {" `nl`+ "ARCHS = \"$(ARCHS_STANDARD_32_BIT)\";" `nl`+ "GCC_C_LANGUAGE_STANDARD = c99;" `nl`+ "GCC_WARN_ABOUT_RETURN_TYPE = YES;" `nl`+ "GCC_WARN_UNUSED_VARIABLE = YES;" `nl`+ "OTHER_LDFLAGS = \"-ObjC\";" `nl`+ "PREBINDING = NO;" `nl`+ "SDKROOT = iphoneos4.0;" `nl`+ "};" `nl`+ "name = Release;" `nl`+ "};" `nl`+ "/* End XCBuildConfiguration section */" `nl`+ "" `nl`+ "/* Begin XCConfigurationList section */" `nl`+ "1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget \"KitDeps\" */ = {" `nl`+ "isa = XCConfigurationList;" `nl`+ "buildConfigurations = (" `nl`+ "1DEB921F08733DC00010E9CD /* Debug */," `nl`+ "1DEB922008733DC00010E9CD /* Release */," `nl`+ ");" `nl`+ "defaultConfigurationIsVisible = 0;" `nl`+ "defaultConfigurationName = Release;" `nl`+ "};" `nl`+ "1DEB922208733DC00010E9CD /* Build configuration list for PBXProject \"KitDepsCustom\" */ = {" `nl`+ "isa = XCConfigurationList;" `nl`+ "buildConfigurations = (" `nl`+ "1DEB922308733DC00010E9CD /* Debug */," `nl`+ "1DEB922408733DC00010E9CD /* Release */," `nl`+ ");" `nl`+ "defaultConfigurationIsVisible = 0;" `nl`+ "defaultConfigurationName = Release;" `nl`+ "};" `nl`+ "/* End XCConfigurationList section */" `nl`+ "};" `nl`+ "rootObject = 0867D690FE84028FC02AAC07 /* Project object */;" `nl`+ "}"+ + + main = do+ putStrLn $ projectPbxProj "" "" "" "" ""+
+ Kit/XCode/XCConfig.hs view
@@ -0,0 +1,56 @@+module Kit.XCode.XCConfig where++ import Kit.Util++ import qualified Data.Map as M+ import Data.Maybe+ import Data.Either+ import Data.List+ + import Data.String.Utils++ type XCCSetting = (String, String)+ type XCCInclude = String+ + data XCConfig = XCC { configName :: String, configSettings :: M.Map String String, configIncludes :: [XCCInclude] } deriving (Eq, Show)+ + includeStart = "#include "+ + parseLine :: String -> Maybe (Either XCCSetting XCCInclude)+ parseLine l | includeStart `isPrefixOf` l = Just . Right . fromJust $ stripPrefix includeStart l+ parseLine l | "=" `isInfixOf` l = fmap (Left) $ breakAround '=' l+ where breakAround a xs = let (q,r) = break (a ==) xs in nonEmptyPair (strip q, strip $ drop 1 r)+ nonEmptyPair ([], _) = Nothing+ nonEmptyPair (_, []) = Nothing+ nonEmptyPair a@_ = Just a+ parseLine _ = Nothing+ + settingToString :: XCCSetting -> String+ settingToString (a, b) = a ++ " = " ++ b+ + includeToString :: XCCInclude -> String+ includeToString a = includeStart ++ a+ + -- TODO incorporate the config name+ configToString :: XCConfig -> String+ configToString (XCC _ settings includes) = stringJoin "\n" $ (map includeToString includes) ++ (map settingToString $ M.toList settings)+ + fileContentsToXCC :: String -> String -> XCConfig+ fileContentsToXCC name content = let ls = lines content+ (settings, includes) = partitionEithers (ls >>= (maybeToList . parseLine))+ in XCC name (M.fromList settings) includes++ configAsMap :: XCConfig -> M.Map String [(String, String)]+ configAsMap (XCC name settings _) = M.map (return . (,) name) settings+ + multiConfig :: String -> [XCConfig] -> XCConfig+ multiConfig name configs = XCC name settings includes+ where includes = configs >>= configIncludes+ settingsWithName = foldl (M.unionWith (++)) M.empty (map configAsMap configs)+ aggregateSettings = M.mapWithKey f settingsWithName+ where f key namesAndValues = stringJoin " " $ map ((\x -> "$(" ++ x ++ "_" ++ key ++ ")") . fst) namesAndValues+ individualSettings = M.fromList $ concatMap f $ M.toList settingsWithName+ where f (key, namesAndValues) = map (g key) namesAndValues+ g key (n, v) = (n ++ "_" ++ key, v)+ settings = M.union aggregateSettings individualSettings+
+ LICENSE view
@@ -0,0 +1,1 @@+TODO
+ Main.hs view
@@ -0,0 +1,10 @@+module Main where+ import Kit.Main+ import qualified Data.Map as M+ import Kit.XCode.XCConfig+ + conf1 = XCC "conf1" (M.fromList [("A", "1")]) []+ conf2 = XCC "conf2" (M.fromList [("A", "2")]) []+ + --main = putStrLn $ show $ multiConfig "combo" [conf1, conf2]+ main = kitMain
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ kit.cabal view
@@ -0,0 +1,16 @@+Name: kit+Version: 0.2+Synopsis: A dependency manager for XCode (Objective-C) projects+Description: A dependency manager for XCode (Objective-C) projects+Category: Development+License: BSD3+License-file: LICENSE+Author: Nick Partridge+Maintainer: nkpart@gmail.com+Build-Type: Simple+Cabal-Version: >=1.2+Executable kit+ Main-is: Main.hs+ Other-Modules: Kit.JSON, Kit.Kit, Kit.Main, Kit.Package, Kit.Project, Kit.Repository, Kit.Spec, Kit.Util, Kit.XCode.Builder, Kit.XCode.Common, Kit.XCode.Prefix, Kit.XCode.ProjectFile, Kit.XCode.XCConfig+ Build-Depends: base >= 3 && < 5, HTTP >= 4000.0.9, network, bytestring, filepath, directory, process, json, mtl, cmdargs, MissingH, containers, QuickCheck+