Commit daafe021 authored by Michael Hanus 's avatar Michael Hanus
Browse files

CPM updated

parent 66a860b7
......@@ -437,6 +437,32 @@ In this case, one can mark those functions with the compiler pragma
so that CPM will not generate tests for them.
\subsection{Adding Packages to the Central Package Index}
\label{sec:adding-a-package}
When you have your package ready and want to use it in other packages,
it must be added to the central package index so that CPM can find it
when searching for packages. For this purpose, you can use the
\ccode{cpm add} command:
%
\begin{lstlisting}
> cpm add mypackage
\end{lstlisting}
%
In this case, \code{mypackage} is the name of the directory containing
you package. In particular, the JSON file \ccode{mypackage/package.json} must
contain the metadata of the package (see also Section~\ref{sec:reference}).
This command copies your package into your local copy of the central package
index so that it can be used in other packages.
If you want to replace this copy by an improved version
of the same package, you have to provide the option
\code{-f} or \code{--force}.
Note that this command makes your package only available on your local
system. If you want to publish your package so that it can be used
by other CPM users, follow the instruction described next.
\subsection{Publishing a Package}
\label{sec:publishing-a-package}
......@@ -486,14 +512,12 @@ to the tag \ccode{v\$version\$}.
After you have published the files for your new package version, you
have to add the corresponding package specification to the central
package index. The central package index is just a Git repository
containing a directory for each package, which contain subdirectories
for all versions of that package which in turn contain the package
specification files. So the specification for version $1.0.5$ of the
\code{json} package would be located in
\code{json/1.0.5/package.json}. If you have access to the Git
repository containing the central package index, then you can add the
package specification yourself. Otherwise, send your package
package index. This can be done with the \ccode{cpm add} command
(see Section~\ref{sec:adding-a-package}).
If you have access to the Git
repository containing the central package index, then you can push
the modified version of this Git repository.
Otherwise, send your package
specification file to \url{packages@curry-language.org} in order to
publish it.
......@@ -547,11 +571,17 @@ one can execute the command
\section{Some CPM Internals}
\label{sec:internals}
CPM's central package index is a Git repository containing package specification
files. A copy of this Git repository is stored on your local system in the
\code{\$HOME/.cpm/index} directory, unless you changed the location using the
\code{REPOSITORY_PATH} setting. CPM uses the package index when searching for
and installing packages and during dependency resolution.
CPM's central package index is a Git repository containing package
specification files. A copy of this Git repository is stored on your
local system in the \code{\$HOME/.cpm/index} directory, unless you
changed the location using the \code{REPOSITORY_PATH} setting. CPM
uses the package index when searching for and installing packages and
during dependency resolution.
This index contains a directory for each package, which contain subdirectories
for all versions of that package which in turn contain the package
specification files. So the specification for version $1.0.5$ of the
\code{json} package would be located in
\code{json/1.0.5/package.json}.
When a package is installed on the system, it is stored in the
\emph{global package cache}.
......@@ -754,6 +784,14 @@ Any arguments are passed verbatim to the compiler.
\item[\fbox{\code{link $source$}}] Can be used to replace a dependency of the
current package using a local copy, see Section~\ref{sec:cpm-link} for details.
\item[\fbox{\code{add $source$ [--force]}}]
Copies the package contained in $source$ into the local copy
of the central package index so that it can be used by other packages
in the local environment
(see Section~\ref{sec:adding-a-package} for details).
The option \ccode{--force} allows to overwrite existing copies
in the central package index.
\item[\fbox{\code{clean}}] Cleans the current package from the
generated auxiliariy files, e.g., intermediate Curry files,
installed dependent packages, etc.
......
......@@ -22,9 +22,10 @@ import Boxes (table, render)
import OptParse
import CPM.ErrorLogger
import CPM.FileUtil ( fileInPath, joinSearchPath, safeReadFile, whenFileExists
, ifFileExists, inDirectory, removeDirectoryComplete )
, ifFileExists, inDirectory, removeDirectoryComplete
, copyDirectory )
import CPM.Config ( Config ( packageInstallDir, binInstallDir
, appPackageDir, curryExec )
, repositoryDir, appPackageDir, curryExec )
, readConfigurationWithDefault, showCompilerVersion )
import CPM.PackageCache.Global ( GlobalCache, readInstalledPackagesFromDir
, installFromZip, checkoutPackage
......@@ -32,7 +33,8 @@ import CPM.PackageCache.Global ( GlobalCache, readInstalledPackagesFromDir
import CPM.Package
import CPM.Resolution ( isCompatibleToCompiler, showResult )
import CPM.Repository ( Repository, readRepository, findVersion, listPackages
, findLatestVersion, updateRepository, searchPackages )
, findLatestVersion, updateRepository, searchPackages
, updateRepositoryCache )
import CPM.PackageCache.Runtime ( dependencyPathsSeparate, writePackageConfig )
import CPM.PackageCopy
import CPM.Diff.API as APIDiff
......@@ -44,7 +46,7 @@ cpmBanner :: String
cpmBanner = unlines [bannerLine,bannerText,bannerLine]
where
bannerText =
"Curry Package Manager <curry-language.org/tools/cpm> (version of 04/05/2017)"
"Curry Package Manager <curry-language.org/tools/cpm> (version of 19/05/2017)"
bannerLine = take (length bannerText) (repeat '-')
main :: IO ()
......@@ -76,7 +78,7 @@ runWithArgs opts = do
Left err -> do putStrLn $ "Error reading .cpmrc settings: " ++ err
exitWith 1
Right c' -> return c'
let getRepoGC = getRepository config >>= \repo ->
let getRepoGC = readRepository config >>= \repo ->
getGlobalCache config repo >>= \gc -> return (repo,gc)
setLogLevel $ optLogLevel opts
(msgs, result) <- case optCommand opts of
......@@ -86,9 +88,11 @@ runWithArgs opts = do
Exec o -> exec o config getRepoGC
Doc o -> docCmd o config getRepoGC
Test o -> test o config getRepoGC
Link o -> linkCmd o config
Add o -> addCmd o config
Clean -> cleanPackage Info
New o -> newPackage o
_ -> do repo <- getRepository config
_ -> do repo <- readRepository config
case optCommand opts of
List o -> listCmd o config repo
Search o -> search o config repo
......@@ -102,7 +106,6 @@ runWithArgs opts = do
Diff o -> diff o config repo globalCache
Uninstall o -> uninstall o config repo globalCache
Upgrade o -> upgrade o config repo globalCache
Link o -> link o config repo globalCache
_ -> error "Internal command processing error!"
mapIO showLogEntry msgs
let allOk = all (levelGte Info) (map logLevelOf msgs) &&
......@@ -119,15 +122,6 @@ getGlobalCache config repo = do
exitWith 1
Right gc -> return gc
getRepository :: Config -> IO Repository
getRepository config = do
(repo, repoErrors) <- readRepository config
if null repoErrors
then return repo
else do putStrLn "Problems while reading the package index:"
mapIO putStrLn repoErrors
exitWith 1
data Options = Options
{ optLogLevel :: LogLevel
, optDefConfig :: [(String,String)]
......@@ -147,6 +141,7 @@ data Command
| Search SearchOptions
| Upgrade UpgradeOptions
| Link LinkOptions
| Add AddOptions
| Exec ExecOptions
| Doc DocOptions
| Test TestOptions
......@@ -193,6 +188,11 @@ data UpgradeOptions = UpgradeOptions
data LinkOptions = LinkOptions
{ lnkSource :: String }
data AddOptions = AddOptions
{ addSource :: String
, forceAdd :: Bool
}
data NewOptions = NewOptions
{ projectName :: String }
......@@ -259,6 +259,11 @@ linkOpts s = case optCommand s of
Link opts -> opts
_ -> LinkOptions ""
addOpts :: Options -> AddOptions
addOpts s = case optCommand s of
Add opts -> opts
_ -> AddOptions "" False
newOpts :: Options -> NewOptions
newOpts s = case optCommand s of
New opts -> opts
......@@ -386,7 +391,9 @@ optionParser = optParser
(\a -> Right $ a { optCommand = Upgrade (upgradeOpts a) })
upgradeArgs
<|> command "link" (help "Link a package to the local cache") Right
linkArgs ) )
linkArgs
<|> command "add" (help "Add a package to the local repository") Right
addArgs ) )
where
checkoutArgs cmd =
arg (\s a -> Right $ a { optCommand = cmd (checkoutOpts a)
......@@ -580,6 +587,17 @@ optionParser = optParser
( metavar "SOURCE"
<> help "The directory to link" )
addArgs =
flag (\a -> Right $ a { optCommand =
Add (addOpts a) { forceAdd = True } })
( short "f"
<> long "force"
<> help "Force, i.e., overwrite existing package" )
<.> arg (\s a -> Right $ a { optCommand =
Add (addOpts a) { addSource = s } })
( metavar "SOURCE"
<> help "The directory to add to the local repository" )
-- Check if operating system executables we depend on are present on the
-- current system.
checkExecutables :: IO [String]
......@@ -883,14 +901,50 @@ upgrade (UpgradeOptions (Just pkg)) cfg repo gc =
upgradeSinglePackage cfg repo gc specDir pkg
link :: LinkOptions -> Config -> Repository -> GlobalCache
-> IO (ErrorLogger ())
link (LinkOptions src) _ _ _ =
linkCmd :: LinkOptions -> Config -> IO (ErrorLogger ())
linkCmd (LinkOptions src) _ =
tryFindLocalPackageSpec "." |>= \specDir ->
cleanCurryPathCache specDir |>
log Info ("Linking '" ++ src ++ "' into local package cache") |>
linkToLocalCache src specDir
--- `add` command: copy the given package to the repository index
--- and package installation directory so that it is available as
--- any other package.
addCmd :: AddOptions -> Config -> IO (ErrorLogger ())
addCmd (AddOptions pkgdir force) config = do
dirExists <- doesDirectoryExist pkgdir
if dirExists
then loadPackageSpec pkgdir |>= \pkgSpec ->
(copyPackage pkgSpec >> succeedIO ()) |>
log Info ("Package in directory '" ++ pkgdir ++
"' installed into local repository")
else log Critical ("Directory '" ++ pkgdir ++ "' does not exist.") |>
succeedIO ()
where
copyPackage pkg = do
let pkgName = name pkg
pkgVersion = version pkg
pkgIndexDir = pkgName </> showVersion pkgVersion
pkgRepositoryDir = repositoryDir config </> pkgIndexDir
pkgInstallDir = packageInstallDir config </> packageId pkg
exrepodir <- doesDirectoryExist pkgRepositoryDir
when (exrepodir && not force) $ error $
"Package repository directory '" ++
pkgRepositoryDir ++ "' already exists!\n" ++ useForce
expkgdir <- doesDirectoryExist pkgInstallDir
when expkgdir $
if force then removeDirectoryComplete pkgInstallDir
else error $ "Package installation directory '" ++
pkgInstallDir ++ "' already exists!\n" ++ useForce
infoMessage $ "Create directory: " ++ pkgRepositoryDir
createDirectoryIfMissing True pkgRepositoryDir
copyFile (pkgdir </> "package.json") (pkgRepositoryDir </> "package.json")
copyDirectory pkgdir pkgInstallDir
updateRepositoryCache config
useForce = "Use option '-f' or '--force' to overwrite it."
--- `doc` command: run `curry doc` on the modules provided as an argument
--- or, if they are not given, on exported modules (if specified in the
--- package), on the main executable (if specified in the package),
......
--------------------------------------------------------------------------------
------------------------------------------------------------------------------
--- This module implements functionality surrounding the package *repository*.
--- The repository is the index of all packages known to the package manager. It
--- contains metadata about the packages, such as their names, versions
--- The repository is the index of all packages known to the package manager.
--- It contains metadata about the packages, such as their names, versions
--- dependencies and where they can be acquired. The repository does not contain
--- the actual packages. For a list of packages that are currently installed
--- locally, you can consult the *database*.
--------------------------------------------------------------------------------
------------------------------------------------------------------------------
module CPM.Repository
( Repository
......@@ -17,18 +17,22 @@ module CPM.Repository
, searchPackages
, listPackages
, updateRepository
, updateRepositoryCache
) where
import Char (toLower)
import Char ( toLower )
import Directory
import Either
import List
import FilePath
import List
import ReadShowTerm ( readQTermFile, writeQTermFile )
import System ( exitWith )
import CPM.Config (Config, repositoryDir, packageIndexRepository)
import CPM.Config ( Config, repositoryDir, packageIndexRepository )
import CPM.ErrorLogger
import CPM.Package
import CPM.FileUtil ( checkAndGetDirectoryContents, inDirectory )
import CPM.FileUtil ( checkAndGetDirectoryContents, inDirectory
, whenFileExists )
import CPM.Resolution ( isCompatibleToCompiler )
data Repository = Repository [Package]
......@@ -109,11 +113,24 @@ findVersion repo p v =
allPackages :: Repository -> [Package]
allPackages (Repository ps) = ps
--- Reads all package specifications from the default repository
--- Reads all package specifications from the default repository.
--- Use the cache if is present or update the cache after reading.
--- If some errors occur, show them and terminate with exit status.
---
--- @param cfg the configuration to use
readRepository :: Config -> IO (Repository, [String])
readRepository cfg = readRepositoryFrom (repositoryDir cfg)
readRepository :: Config -> IO Repository
readRepository cfg = do
mbrepo <- readRepositoryCache cfg
case mbrepo of
Nothing -> do
infoMessage "Writing the repository cache..."
(repo, repoErrors) <- readRepositoryFrom (repositoryDir cfg)
if null repoErrors
then writeRepositoryCache cfg repo >> return repo
else do putStrLn "Problems while reading the package index:"
mapIO putStrLn repoErrors
exitWith 1
Just repo -> return repo
--- Reads all package specifications from a repository.
---
......@@ -124,7 +141,8 @@ readRepositoryFrom path = do
pkgDirs <- checkAndGetDirectoryContents path
pkgPaths <- return $ map (path </>) $ filter dirOrSpec pkgDirs
verDirs <- mapIO checkAndGetDirectoryContents pkgPaths
verPaths <- return $ concat $ map (\(d, p) -> map (d </>) (filter dirOrSpec p)) $ zip pkgPaths verDirs
verPaths <- return $ concatMap (\ (d, p) -> map (d </>) (filter dirOrSpec p))
$ zip pkgPaths verDirs
specPaths <- return $ map (</> "package.json") verPaths
specs <- mapIO readPackageFile specPaths
when (null (lefts specs)) $ debugMessage "Finished reading repository"
......@@ -138,25 +156,70 @@ readRepositoryFrom path = do
Left err -> Left $ "Problem reading '" ++ f ++ "': " ++ err
Right s -> Right s
dirOrSpec d = (not $ isPrefixOf "." d) && takeExtension d /= ".md"
dirOrSpec d = (not $ isPrefixOf "." d) && takeExtension d /= ".md" &&
d /= repositoryCacheFileName
--- Updates the package index from the central Git repository.
updateRepository :: Config -> IO (ErrorLogger ())
updateRepository cfg = do
cleanRepositoryCache cfg
gitExists <- doesDirectoryExist $ (repositoryDir cfg) </> ".git"
if gitExists
then do
c <- inDirectory (repositoryDir cfg) $
execQuietCmd $ (\q -> "git pull " ++ q ++ " origin master")
if c == 0
then log Info "Successfully updated repository"
then do updateRepositoryCache cfg
log Info "Successfully updated repository"
else failIO $ "Failed to update git repository, return code " ++ show c
else do
c <- inDirectory (repositoryDir cfg) $ execQuietCmd cloneCommand
if c == 0
then log Info "Successfully updated repository"
then do updateRepositoryCache cfg
log Info "Successfully updated repository"
else failIO $ "Failed to update git repository, return code " ++ show c
where
cloneCommand q = unwords ["git clone", q, packageIndexRepository cfg, "."]
------------------------------------------------------------------------------
-- Operations implementing the repository cache for faster reading.
--- The local file name containing the repository cache as a Curry term.
repositoryCacheFileName :: String
repositoryCacheFileName = "repository_cache"
--- The file containing the repository cache as a Curry term.
repositoryCache :: Config -> String
repositoryCache cfg = repositoryDir cfg </> repositoryCacheFileName
--- Updates the repository cache with the current repository index.
updateRepositoryCache :: Config -> IO ()
updateRepositoryCache cfg = do
cleanRepositoryCache cfg
repo <- readRepository cfg
writeRepositoryCache cfg repo
--- Stores the given repository in the cache.
writeRepositoryCache :: Config -> Repository -> IO ()
writeRepositoryCache cfg repo =
writeQTermFile (repositoryCache cfg) repo
--- Reads the given repository from the cache.
readRepositoryCache :: Config -> IO (Maybe Repository)
readRepositoryCache cfg = do
let cf = repositoryCache cfg
excache <- doesFileExist cf
if excache
then debugMessage ("Reading repository cache from '" ++ cf ++ "'...") >>
catch (readQTermFile cf >>= \t -> return $!! Just t)
(\_ -> cleanRepositoryCache cfg >> return Nothing)
else return Nothing
--- Cleans the repository cache.
cleanRepositoryCache :: Config -> IO ()
cleanRepositoryCache cfg = do
let cachefile = repositoryCache cfg
whenFileExists cachefile $ removeFile cachefile
------------------------------------------------------------------------------
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment