Commit 3f2d1751 authored by Michael Hanus 's avatar Michael Hanus
Browse files

CPM added to tools

parent e7499a7b
......@@ -88,7 +88,7 @@ $(uninstall_TOOLDIRS):
# Testing the tools
# Tools with test suites:
TESTTOOLS = optimize currypp runcurry currycheck spicey xmldata
TESTTOOLS = optimize currypp runcurry currycheck spicey xmldata cpm
# run the test suites to check the tools
.PHONY: runtest
......
# Makefile for Curry Package Manager (CPM)
# The tool name of the application:
TOOL = $(BINDIR)/cpm
# The default options for the REPL
export REPL_OPTS = --noreadline :set -time
# Source modules of CPM:
DEPS = src/CPM/*.curry src/CPM/*/*.curry
.PHONY: all compile install clean uninstall runtest
all: install
install: compile
rm -f $(TOOL)
cd $(BINDIR) && ln -s ../currytools/cpm/src/CPM.Main $(notdir $(TOOL))
compile: src/CPM.Main
clean:
rm -Rf src/.curry vendor/*/src/.curry
rm src/CPM.Main
uninstall: clean
rm -f $(TOOL)
src/CPM.Main: $(DEPS)
@echo Root location of Curry system: $(ROOT)
@if [ ! -d "$(ROOT)" ] ; then echo "Error: not a valid directory!" ; exit 1; fi
@export CURRYPATH=""; \
for i in `ls vendor`; do \
export CURRYPATH="$$CURRYPATH:`pwd`/vendor/$$i/src"; \
done; \
echo "Set CURRYPATH to $$CURRYPATH"; \
cd src; $(REPL) $(REPL_OPTS) :l CPM.Main :save :quit
runtest:
@export CURRYPATH=""; \
for i in `ls vendor`; do \
export CURRYPATH="$$CURRYPATH:`pwd`/vendor/$$i/src"; \
done; \
cd src; $(REPL) check CPM.Package CPM.Resolution CPM.LookupSet
.PHONY: doc
doc:
@export CURRYPATH=""; \
for i in `ls vendor`; do \
export CURRYPATH="$$CURRYPATH:`pwd`/vendor/$$i/src"; \
done; \
export CURRYPATH="$$CURRYPATH:`pwd`/src"; \
$(REPL) doc cdoc CPM.Main
.PHONY: manual
manual:
pdflatex -output-directory=docs docs/manual.tex
# The Curry Package Manager
This repository contains the Curry package manager (CPM).
## Quick Start
To build the Curry package manager, you need to run `make` inside this
directory. The `Makefile` assumes that the `curry` executable and `git` are on
your path. If the build was successful, a `cpm` binary will be placed in the
directory `~/.cpm/bin` (which is also the directory where CPM installs
binaries of tools distributed in packages). Therefore, you should
add this directory to your path. Afterwards, run
`cpm update` to clone a copy of the central package index repository. More
information can be found in the manual, see the `docs` directory.
## Documentation
Use `make manual` to generate a PDF version of the manual. A working LaTeX
installation is required. `make doc` generates the CurryDoc documentation for
the CPM source code in the `cdoc` directory.
## Contributing
Please run the tests using `make test` before publishing your changes. You
should also run the performance tests when you make changes to the API or
behavior comparison modules or the resolution algorithm. To run the performance
tests, build the performance test program using `make buildperf`. You can then
use `bin/perftest` to execute the different performance test.
To test the API comparison algorithm, use `bin/perftest api -n NUMBER`, where
`NUMBER` is the number of added, changed and removed functions and types each
that you want to compare. Note that when you specify 1000, the API comparison
will compare 6000 elements: 1000 added functions, 1000 removed functions, 1000
changed functions, 1000 added types, 1000 removed types, and 1000 changed types.
The behavior comparison algorithm can be tested using `bin/perftest behavior -t
T -f F`, where `F` is the number of functions to compare and `T` is the depth to
which the type of each function's argument is nested. For example, if `T` is set
to 2, each generated function will take a type `Nested1`, which is defined as
follows:
```haskell
data Nested1 = Nested1 Nested2
data Nested2 = Nested2 String
```
To test the resolution algorithm, you need a set of test data, which you can
find in the [cpm-perf-test-data](1) repository. Make sure that `packages.term`
from thate repository is available in the current directory and then run
`bin/perftest resolution --packages=P`, where `P` is a comma-separated list of
package identifiers. A complete list of package identifiers available in the
test data set can be found in the `packages.txt` file alongside `packages.term`.
A good set of packages to start with is the following:
- `express-4.14.0` has 1,759 dependencies available in 23,295 different versions.
Resolution succeeds relatively quickly, since a solution can be found early in
the candidate tree.
- `express-3.9.0` has 1,794 dependencies available in 23,286 different versions.
Resolution fails in about a quarter second on KiCS2 since a package is missing
in the sample data set.
- `chalk-1.1.3` only has 8 dependencies available in 65 different versions.
Resolution succeeds very quickly.
- `request-2.74.0` has 1,789 dependencies available in 23,229 different
versions. Resolution still succeeds quickly on KiCS2, but takes over a second
on PAKCS.
- `mocha-1.21.5` has 1,789 dependencies available in 23,229 different versions.
Resolution fails with a dependency conflict in about 4.5 seconds on KiCS2, but
fails to finish in a reasonable timeframe on PAKCS.
- `karma-1.2.0` has 1,850 dependencies available in 24,264 different versions.
Currently, the resolution algorithm is too slow and does not arrive at a
solution in a reasonable timeframe.
This diff is collapsed.
module MeasureNestingDepth where
import Directory
import Debug
import List
import Distribution
import FilePath
import AbstractCurry.Types
import AbstractCurry.Select
import AbstractCurry.Files
sysLibPath = case curryCompiler of
"pakcs" -> installDir </> "lib"
"kics" -> installDir </> "src" </> "lib"
"kics2" -> installDir </> "lib"
_ -> error "unknown curryCompiler"
main = do
putStrLn "Reading all standard libary modules..."
mods <- readAllStandardModules
putStrLn "Calculating depths..."
depths <- return $ concatMap (typeDepths mods . snd) mods
orderedDepths <- return $ sortBy (\a b -> snd a >= snd b) depths
putStrLn $ "The following are the 40 type with the deepest level of nesting " ++
"out of all " ++ (show $ length depths) ++ " types."
putStrLn $ show $ take 40 orderedDepths
typeDepths :: [(String, CurryProg)] -> CurryProg -> [(QName, Int)]
typeDepths progs (CurryProg _ _ ts _ _) = zip (map typeName ts) (map (typeDepth [] progs) ts)
typeDepth :: [QName] -> [(String, CurryProg)] -> CTypeDecl -> Int
typeDepth seen progs (CType n _ _ cs) = if n `elem` seen
then 0
else 1 + (foldl max 0 $ map (typeConsDepth (n:seen) progs) cs)
typeDepth seen progs (CTypeSyn n _ _ e) = if n `elem` seen
then 0
else 1 + (typeExprDepth (n:seen) progs e)
typeDepth seen progs (CNewType n _ _ c) = if n `elem` seen
then 0
else 1 + (typeConsDepth (n:seen) progs c)
typeConsDepth :: [QName] -> [(String, CurryProg)] -> CConsDecl -> Int
typeConsDepth seen progs (CCons _ _ es) = foldl max 0 $ map (typeExprDepth seen progs) es
typeConsDepth seen progs (CRecord _ _ fs) = foldl max 0 $ map fieldDepth fs
where
fieldDepth (CField _ _ e) = typeExprDepth seen progs e
typeExprDepth :: [QName] -> [(String, CurryProg)] -> CTypeExpr -> Int
typeExprDepth _ _ (CTVar _) = 0
typeExprDepth seen progs (CFuncType e1 e2) = max (typeExprDepth seen progs e1) (typeExprDepth seen progs e2)
typeExprDepth seen progs e@(CTCons n@(mod, _) es) = typeDepth seen progs ity
where
ty = case predefinedType n of
Nothing -> case lookup mod progs of
Nothing -> error $ "Module " ++ mod ++ " not found"
Just p -> case find ((== n) . typeName) $ types p of
Nothing -> error $ "Type " ++ (show n) ++ " not found in module " ++ mod
Just x -> x
Just x -> x
ity = instantiateType e ty
instantiateType :: CTypeExpr -> CTypeDecl -> CTypeDecl
instantiateType (CTCons _ es) (CType nt v vs cs) =
CType nt v vs ics
where
ics = map (instantiateConstructor $ zip vs es) cs
instantiateType _ a@(CTypeSyn _ _ _ _) = a
instantiateType _ a@(CNewType _ _ _ _) = a
instantiateConstructor :: [(CTVarIName, CTypeExpr)] -> CConsDecl -> CConsDecl
instantiateConstructor vs (CCons n v es) = CCons n v $ map (\e -> foldl (flip replaceVar) e vs) es
instantiateConstructor vs (CRecord n v fs) = CRecord n v $ map instantiateField fs
where
instantiateField (CField n' v' e) = CField n' v' $ foldl (flip replaceVar) e vs
replaceVar :: (CTVarIName, CTypeExpr) -> CTypeExpr -> CTypeExpr
replaceVar (b, e) (CTVar a) | a == b = e
| otherwise = CTVar a
replaceVar v (CFuncType e1 e2) = CFuncType (replaceVar v e1) (replaceVar v e2)
replaceVar v (CTCons n es) = CTCons n $ map (replaceVar v) es
readAllStandardModules :: IO [(String, CurryProg)]
readAllStandardModules = do
entries <- getDirectoryContents sysLibPath
mods <- mapIO (readMods "" sysLibPath) $ filter (not . isIrrelevant) entries
return $ concat mods
where
readMods :: String -> String -> String -> IO [(String, CurryProg)]
readMods prefix base f = do
isDir <- doesDirectoryExist $ base </> f
if isDir
then do
entries <- getDirectoryContents $ base </> f
mods <- mapIO (readMods (prefixIt prefix f) $ base </> f) $ filter (not . isIrrelevant) entries
return $ concat mods
else do
callFrontendWithParams ACY (setQuiet True defaultParams) $ stripCurrySuffix $ prefixIt prefix f
prog <- readCurry $ prefixIt prefix f
return [(stripCurrySuffix $ prefixIt prefix f, prog)]
prefixIt prefix m | prefix == "" = m
| otherwise = prefix ++ "." ++ m
isIrrelevant :: String -> Bool
isIrrelevant d = "." `isPrefixOf` d || (takeExtensions d /= "" && takeExtensions d /= ".curry") || d == "dist" || d == "Makefile"
predefinedType :: (String, String) -> Maybe CTypeDecl
predefinedType x = case x of
("Prelude", "[]") -> Just $ CType ("Prelude", "[]") Public [(0, "a")] [
CCons ("Prelude", "[]") Public []
, CCons ("Prelude", ":") Public [CTVar (0, "a"), CTCons ("Prelude", "[]") [CTVar (0, "a")]]]
("Prelude", "(,)") -> Just $ CType ("Prelude", "(,)") Public [(0, "a"), (1, "b")] [
CCons ("Prelude", "(,)") Public [CTVar (0, "a"), CTVar (1, "b")]]
("Prelude", "(,,)") -> Just $ CType ("Prelude", "(,,)") Public [(0, "a"), (1, "b"), (2, "c")] [
CCons ("Prelude", "(,,)") Public [CTVar (0, "a"), CTVar (1, "b"), CTVar (2, "c")]]
("Prelude", "(,,,)") -> Just $ CType ("Prelude", "(,,,)") Public [(0, "a"), (1, "b"), (2, "c"), (3, "d")] [
CCons ("Prelude", "(,,,)") Public [CTVar (0, "a"), CTVar (1, "b"), CTVar (2, "c"), CTVar (3, "d")]]
("Prelude", "(,,,,)") -> Just $ CType ("Prelude", "(,,,,)") Public [(0, "a"), (1, "b"), (2, "c"), (3, "d"), (4, "e")] [
CCons ("Prelude", "(,,,,)") Public [CTVar (0, "a"), CTVar (1, "b"), CTVar (2, "c"), CTVar (3, "d"), CTVar (4, "e")]]
("Prelude", "()") -> Just $ CType ("Prelude", "()") Public [] [CCons ("Prelude", "()") Public []]
_ -> Nothing
# This script reads specifications from a copy of the npm package index that
# needs to be running in a local CouchDB instance. It extracts all versions of
# a fixed list of packages (see EXTRACT_PACKAGES) and all versions of all
# transitive dependencies of those packages. The extracted specifications are
# written to package.json files in a cpm index directory structure. Dependency
# constraints and version numbers are translated if possible. Some constructs
# that are not recognized by cpm are simply replaced by approximations while
# others will lead to the package version being excluded from the index
# entirely.
require 'couchrest'
require 'json'
require 'parslet'
if ARGV.length < 1
puts "Please specify an output directory!"
exit
end
out_dir = ARGV[0]
EXTRACT_PACKAGES = [
'express', 'karma', 'chalk', 'request', 'express-session', 'pm2'
]
class DependencySpec < Parslet::Parser
rule(:space) { match('\s').repeat(1) }
rule(:space?) { space.maybe }
rule(:pre) { str('-').maybe >> match('[a-zA-Z]') >> match('[a-zA-Z0-9]').repeat }
rule(:version) { str('*') | match('[0-9x]').repeat(1).as(:maj) >> (str('.') >> match('[0-9x]').repeat(1)).maybe.as(:min) >> (str('.') >> match('[0-9x]').repeat(1)).maybe.as(:pat) >> pre.maybe.as(:pre) }
rule(:disjunction) { (conjunction >> (space? >> str('||') >> space? >> conjunction).repeat).maybe.as(:conjs) }
rule(:conjunction) { (comparison >> (space? >> comparison).repeat).maybe.as(:comps) }
rule(:comparison) { range.as(:range) | lte.as(:lte) | lt.as(:lt) | gte.as(:gte) | gt.as(:gt) | semver.as(:semver) | caret.as(:caret) | eq.as(:eq) | version.as(:bare) }
rule(:range) { version.as(:ver1) >> space? >> str('-') >> space? >> version.as(:ver2) }
rule(:lte) { str('<=') >> space? >> version.as(:ver) }
rule(:lt) { str('<') >> space? >> version.as(:ver) }
rule(:gte) { str('>=') >> space? >> version.as(:ver) }
rule(:gt) { str('>') >> space? >> version.as(:ver) }
rule(:eq) { str('=') >> space? >> version.as(:ver) }
rule(:semver) { str('~') >> space? >> version.as(:ver) }
rule(:caret) { str('^') >> space? >> version.as(:ver) }
root(:disjunction)
end
def format_version_bare(v)
if v == '*'
">= 0.0.0"
elsif !v[:min]
">= #{v[:maj]}.0.0"
elsif v[:min] == '.x'
">= #{v[:maj]}.0.0, < #{v[:maj].to_i + 1}.0.0"
elsif v[:pat] == '.x'
">= #{v[:maj]}#{v[:min]}.0, < #{v[:maj]}#{v[:min].to_i + 1}.0"
elsif !v[:pat]
"#{v[:maj]}#{v[:min]}.0"
elsif !v[:pre]
"#{v[:maj]}#{v[:min]}#{v[:pat]}"
else
"#{v[:maj]}#{v[:min]}#{v[:pat]}#{v[:pre]}"
end
end
def format_version(v)
if v == '*'
"0.0.0"
elsif !v[:min]
"#{v[:maj]}.0.0"
elsif v[:min] == 'x'
"#{v[:maj]}.0.0"
elsif v[:pat] == '.x'
"#{v[:maj]}#{v[:min]}.0"
elsif !v[:pat]
"#{v[:maj]}#{v[:min]}.0"
elsif !v[:pre]
"#{v[:maj]}#{v[:min]}#{v[:pat]}"
else
"#{v[:maj]}#{v[:min]}#{v[:pat]}#{v[:pre]}"
end
end
class CpmTrans < Parslet::Transform
rule(:lte => simple(:x)) { "<= #{x}" }
rule(:lt => simple(:x)) { "< #{x}" }
rule(:gte => simple(:x)) { ">= #{x}" }
rule(:gt => simple(:x)) { "> #{x}" }
rule(:semver => simple(:x)) { "~> #{x}" }
rule(:caret => simple(:x)) { ">= #{x}" }
rule(:eq => simple(:x)) { "= #{x}" }
rule(:bare => subtree(:v)) { format_version_bare(v) }
rule(:ver => subtree(:v)) { format_version(v) }
rule(:range => subtree(:x)) {
">= #{x[:ver1]}, < #{x[:ver2]}"
}
rule(:conjs => subtree(:x)) {
if x.is_a?(Array)
x.join(' || ')
else
x
end
}
rule(:comps => subtree(:x)) {
if x.is_a?(Array)
x.join(', ')
else
x
end
}
end
server = CouchRest.new
db = server.database('registry')
packages = {}
def fetch_package(db, pkg_name, packages)
semver_re = /\d+\.\d+\.\d+(\-[a-zA-Z0-9]*)?/
print '.'
if packages.key? pkg_name
return
end
pkg = db.get(pkg_name)
if !pkg
return
end
packages[pkg_name] = []
pkg[:versions].each do |ver, spec|
if ver !~ semver_re
next
end
packages[pkg_name] << spec
(spec['dependencies'] || {}).each do |dep, constraint|
fetch_package db, dep, packages
end
end
end
EXTRACT_PACKAGES.each do |pkg|
fetch_package db, pkg, packages
end
puts
puts "Total packages: #{packages.length}"
puts "Total package versions: #{packages.collect { |k, v| v.length }.inject(0, :+)}"
print "Writing to #{out_dir}..."
def translate_dependency_spec(d)
CpmTrans.new.apply(DependencySpec.new.parse(d))
end
def translate_dependencies(deps)
Hash[deps.collect do |k, v|
begin
[k, translate_dependency_spec(v)]
rescue
[k, ">= 0.0.0"]
end
end]
end
packages.each do |name, versions|
if !Dir.exists?(File.join(out_dir, name))
Dir.mkdir File.join(out_dir, name)
end
versions.each do |version|
if !Dir.exists?(File.join(out_dir, name, version['version']))
Dir.mkdir File.join(out_dir, name, version['version'])
end
File.open(File.join(out_dir, name, version['version'], 'package.json'), 'w') do |f|
f.write(JSON.dump(
name: version['name'],
version: version['version'],
author: 'test',
synopsis: 'test',
dependencies: translate_dependencies(version['dependencies'] || {})
))
end
end
end
puts 'DONE'
--------------------------------------------------------------------------------
--- This module contains helper functions for dealing with AbstractCurry. In
--- particular, it contains functions that can read modules from a package and
--- its dependencies with all dependencies available to the Curry frontend.
--------------------------------------------------------------------------------
module CPM.AbstractCurry
( loadPathForPackage
, readAbstractCurryFromPath
, readAbstractCurryFromDeps
, transformAbstractCurryInDeps
, applyModuleRenames
) where
import Distribution (FrontendTarget (..), FrontendParams (..), defaultParams
, callFrontendWithParams, setQuiet, setFullPath, sysLibPath
, curryCompiler, installDir, inCurrySubdir, modNameToPath
, inCurrySubdirModule, lookupModuleSource)
import List (intercalate, nub)
import FilePath ((</>), (<.>), takeFileName, replaceExtension)
import AbstractCurry.Files (readAbstractCurryFile, writeAbstractCurryFile)
import AbstractCurry.Pretty (showCProg)
import AbstractCurry.Select (imports)
import AbstractCurry.Transform
import AbstractCurry.Types (CurryProg)
import System
import qualified CPM.PackageCache.Runtime as RuntimeCache
import CPM.Package (Package)
--- Returns the load path for a package stored in some directory
--- w.r.t. the dependent packages
---
--- @param - pkgDir - the package's directory
--- @param - deps - the resolved dependencies of the package
--- @return the full load path for modules in the package or dependent packages
loadPathForPackage :: String -> [Package] -> [String]
loadPathForPackage pkgDir deps =
[pkgDir </> "src"] ++ RuntimeCache.dependencyPathsSeparate deps pkgDir
--- Returns the full load path for a package stored in some directory.
---
--- @param - pkgDir - the package's directory
--- @param - deps - the resolved dependencies of the package
--- @return the full load path for modules in the package or dependent packages
fullLoadPathForPackage :: String -> [Package] -> [String]
fullLoadPathForPackage pkgDir deps =
sysLibPath ++ loadPathForPackage pkgDir deps
--- Reads an AbstractCurry module from a package.
---
--- @param - dir the package's directory
--- @param - deps the resolved dependencies of the package
--- @param - mod the module to read
readAbstractCurryFromPath :: String -> [Package] -> String -> IO CurryProg
readAbstractCurryFromPath pkgDir deps modname = do
let loadPath = fullLoadPathForPackage pkgDir deps
params <- return $ setQuiet True (setFullPath loadPath defaultParams)
callFrontendWithParams ACY params modname
readAbstractCurryFile acyName
where
acyName = inCurrySubdir (pkgDir </> "src" </> modname) ++ ".acy"
--- Reads an AbstractCurry module from a package or one of its dependencies.
---
--- @param dir - the package's directory
--- @param deps - the resolved dependencies of the package
--- @param mod - the module to read
readAbstractCurryFromDeps :: String -> [Package] -> String -> IO CurryProg
readAbstractCurryFromDeps pkgDir deps modname = do
let loadPath = fullLoadPathForPackage pkgDir deps
params <- return $ setQuiet True (setFullPath loadPath defaultParams)
src <- lookupModuleSource loadPath modname
sourceFile <- return $ case src of
Nothing -> error $ "Module not found: " ++ modname
Just (_, file) -> replaceExtension (inCurrySubdirModule modname file) ".acy"
callFrontendWithParams ACY params modname
readAbstractCurryFile sourceFile
--- Applies a transformation function to a module from a package or one of its
--- dependencies and writes the modified module to a file in Curry form.
---
--- @param dir - the package's directory
--- @param deps - the resolved dependencies of the package
--- @param f - the transformation function
--- @param mod - the module to transform
--- @param dest - the destination file for the transformed module
transformAbstractCurryInDeps :: String -> [Package] -> (CurryProg -> CurryProg)
-> String -> String -> IO ()
transformAbstractCurryInDeps pkgDir deps transform modname destFile = do
let loadPath = fullLoadPathForPackage pkgDir deps
params <- return $ setQuiet True (setFullPath loadPath defaultParams)
src <- lookupModuleSource loadPath modname
sourceFile <- return $ case src of
Nothing -> error $ "Module not found: " ++ modname
Just (_, file) -> replaceExtension (inCurrySubdirModule modname file) ".acy"
callFrontendWithParams ACY params modname
acy <- readAbstractCurryFile sourceFile
writeFile destFile $ showCProg (transform acy)
--- Renames all references to some modules in a Curry program.
---
--- @param mods - a map from old to new module names
--- @param prog - the program to modify
applyModuleRenames :: [(String, String)] -> CurryProg -> CurryProg
applyModuleRenames names prog =
updCProg maybeRename (map maybeRename) id id id (updQNamesInCProg rnm prog)
where
maybeRename n = case lookup n names of
Just n' -> n'
Nothing -> n
rnm mn@(mod, n) = case lookup mod names of
Just mod' -> (mod', n)
Nothing -> mn
--------------------------------------------------------------------------------
--- This module defines the data type for CPM's configuration options, the
--- default values for all options, and functions for reading the user's .cpmrc
--- file and merging its contents into the default options.
--------------------------------------------------------------------------------
module CPM.Config
( Config ( Config, packageInstallDir, binInstallDir, repositoryDir
, binPackageDir, packageIndexRepository )
, readConfiguration, readConfigurationWithDefault, defaultConfig ) where
import Char (isSpace)
import Directory (getHomeDirectory, createDirectoryIfMissing)
import FilePath ((</>))
import Function ((***))
import List (splitOn, intersperse)
import Maybe (mapMaybe)
import PropertyFile (readPropertyFile)
import CPM.ErrorLogger
import CPM.FileUtil (ifFileExists)
--- The location of the central package index.
packageIndexURI :: String
packageIndexURI =
"https://git.ps.informatik.uni-kiel.de/curry-packages/cpm-index.git"
-- if you have an ssh access to git.ps.informatik.uni-kiel.de:
-- "ssh://git@git.ps.informatik.uni-kiel.de:55055/curry-packages/cpm-index.git"
--- Data type containing the main configuration of CPM.
data Config = Config {
--- The directory where locally installed packages are stored
packageInstallDir :: String
--- The directory where executable of locally installed packages are stored
, binInstallDir :: String
--- Directory where the package repository is stored
, repositoryDir :: String
--- Directory where the packages with binary installation only are stored
, binPackageDir :: String
--- URL to the package index repository
, packageIndexRepository :: String
}
--- CPM's default configuration values. These are used if no .cpmrc file is found
--- or a new value for the option is not specified in the .cpmrc file.
defaultConfig :: Config
defaultConfig = Config
{ packageInstallDir = "$HOME/.cpm/packages"
, binInstallDir = "$HOME/.cpm/bin"
, repositoryDir = "$HOME/.cpm/index"
, binPackageDir = "$HOME/.cpm/bin_packages"
, packageIndexRepository = packageIndexURI }
--- Reads the .cpmrc file from the user's home directory (if present) and merges
--- its contents into the default configuration. Resolves the $HOME variable
--- after merging and creates any missing directories.
--- May return an error using Left.
readConfiguration :: IO (Either String Config)
readConfiguration = readConfigurationWithDefault []