Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow revision #45

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Here is a simple project configuration:
It defines:

- The project name
- The package set to use to resolve dependencies (this corresponds to a branch or tag of the package set source repository)
- The package set to use to resolve dependencies (this corresponds to a SHA or tag of the package set source repository)
- The package set source repository Git URL (change this if you want to host your own package sets)
- Any dependencies of the project, as a list of names of packages from the package set

Expand Down
138 changes: 93 additions & 45 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ data PackageConfig = PackageConfig
{ name :: PackageName
, depends :: [PackageName]
, set :: Text
, source :: Text
, source :: Repo
} deriving (Show, Generic, Aeson.FromJSON, Aeson.ToJSON)

pathToTextUnsafe :: Turtle.FilePath -> Text
Expand Down Expand Up @@ -96,49 +96,95 @@ writePackageFile =
. packageConfigToJSON

data PackageInfo = PackageInfo
{ repo :: Text
{ repo :: Repo
, version :: Text
, dependencies :: [PackageName]
} deriving (Show, Eq, Generic, Aeson.FromJSON, Aeson.ToJSON)

type PackageSet = Map.Map PackageName PackageInfo

cloneShallow
newtype Repo = Repo { unRepo :: Text } deriving (Show, Eq)

instance Aeson.FromJSON Repo where
parseJSON = fmap Repo . Aeson.parseJSON


instance Aeson.ToJSON Repo where
toJSON = Aeson.toJSON . unRepo


data CloneTarget = CloneTag Text
| CloneSHA Text
deriving (Show)


parseCloneTarget
:: Text
-> Either Text CloneTarget
parseCloneTarget t =
if T.null remainder
then Right (CloneTag t)
else case T.toLower schemeName of
"sha" -> Right (CloneSHA withoutScheme)
"tag" -> Right (CloneTag withoutScheme)
_ -> Left ("Invalid scheme. Expected sha: | tag: but got " <> schemeName)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should probably allow using tags by default and warn about it. Thoughts? cc @kritzcreek

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused. I could see adding a case for "" -> Right (CloneTag withoutScheme). But if they affirmatively provided a scheme that's some unknown, it seems like we should still error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should've read all of the code instead of just this bit :D I see now that we only hit this case if the target contained a : in the first place.

where
(schemeName, remainder) = T.breakOn ":" t
withoutScheme = T.drop 3 remainder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we probably want to use stripPrefix "://" here instead? I could see some very hard to debug behaviour resulting from malformed URLs otherwise.

https://www.stackage.org/haddock/lts-10.5/text-1.2.2.2/Data-Text.html#v:stripPrefix

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should actually just be T.drop 1 here, since the format is now sha:27492fbb19484..., without the slashes?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kritzcreek stripPrefix won't work because there's content before the "://", we're trying to extract the scheme beforehand. If we want to be really paranoid about parsing the URI, we could pull in uri-bytestring, but that is probably overkill.

@hdgarrood I think you're right. Probably should have console tested this since we don't really have a formal test suite for this project.



-- Both tags and SHAs can be treated as immutable so we only have to run this once
cloneShallow
:: Repo
-- ^ repo
-> Text
-- ^ branch/tag
-> CloneTarget
-- ^ tag/SHA
-> Turtle.FilePath
-- ^ target directory
-> IO ExitCode
cloneShallow from ref into =
proc "git"
[ "clone"
, "-q"
, "-c", "advice.detachedHead=false"
, "--depth", "1"
, "-b", ref
, from
, pathToTextUnsafe into
] empty .||. exit (ExitFailure 1)
-> IO ()
cloneShallow (Repo from) tgt into = do
void $ proc "git"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I try this with a SHA hash, I get fatal: Remote branch <sha> not found in upstream origin

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we confirm this works by now?

[ "clone"
, "-q"
, "-c", "advice.detachedHead=false"
, "--no-checkout"
, "-b", tgtText
, from
, pathToTextUnsafe into
] empty .||. exit (ExitFailure 1)
case tgt of
CloneSHA sha ->
inGitRepo $ void $ proc "git"
[ "checkout"
, "-q"
, "-c", "advice.detachedHead=false"
, "--no-checkout"
, sha
] empty .||. exit (ExitFailure 1)
CloneTag _ -> return ()
where
inGitRepo m = sh (pushd into >> m)
tgtText = case tgt of
CloneTag t -> t
CloneSHA t -> t

listRemoteTags
:: Text
:: Repo
-- ^ repo
-> Turtle.Shell Text
listRemoteTags from = let gitProc = inproc "git"
[ "ls-remote"
, "-q"
, "-t"
, from
] empty
in lineToText <$> gitProc
listRemoteTags (Repo from) = let gitProc = inproc "git"
[ "ls-remote"
, "-q"
, "-t"
, from
] empty
in lineToText <$> gitProc

getPackageSet :: PackageConfig -> IO ()
getPackageSet PackageConfig{ source, set } = do
let pkgDir = ".psc-package" </> fromText set </> ".set"
exists <- testdir pkgDir
unless exists . void $ cloneShallow source set pkgDir
unless exists . void $ cloneShallow source (CloneTag set) pkgDir

readPackageSet :: PackageConfig -> IO PackageSet
readPackageSet PackageConfig{ set } = do
Expand All @@ -158,11 +204,13 @@ writePackageSet PackageConfig{ set } =
installOrUpdate :: Text -> PackageName -> PackageInfo -> IO Turtle.FilePath
installOrUpdate set pkgName PackageInfo{ repo, version } = do
let pkgDir = ".psc-package" </> fromText set </> fromText (runPackageName pkgName) </> fromText version
exists <- testdir pkgDir
unless exists . void $ do
echoT ("Updating " <> runPackageName pkgName)
cloneShallow repo version pkgDir
pure pkgDir
echoT ("Updating " <> runPackageName pkgName)
case parseCloneTarget version of
Left parseError -> exitWithErr parseError
Right target -> do
exists <- testdir pkgDir
unless exists . void $ cloneShallow repo target pkgDir
pure pkgDir

getTransitiveDeps :: PackageSet -> [PackageName] -> IO [(PackageName, PackageInfo)]
getTransitiveDeps db deps =
Expand Down Expand Up @@ -198,7 +246,7 @@ getPureScriptVersion = do
| otherwise -> exitWithErr "Unable to parse output of purs --version"
_ -> exitWithErr "Unexpected output from purs --version"

initialize :: Maybe (Text, Maybe Text) -> IO ()
initialize :: Maybe (Text, Maybe Repo) -> IO ()
initialize setAndSource = do
exists <- testfile "psc-package.json"
when exists $ exitWithErr "psc-package.json already exists"
Expand All @@ -212,13 +260,13 @@ initialize setAndSource = do
echoT "(Use --source / --set to override this behavior)"
pure PackageConfig { name = pkgName
, depends = [ preludePackageName ]
, source = "https://github.com/purescript/package-sets.git"
, source = Repo "https://github.com/purescript/package-sets.git"
, set = "psc-" <> pack (showVersion pursVersion)
}
Just (set, source) ->
pure PackageConfig { name = pkgName
, depends = [ preludePackageName ]
, source = fromMaybe "https://github.com/purescript/package-sets.git" source
, source = fromMaybe (Repo "https://github.com/purescript/package-sets.git") source
, set
}

Expand Down Expand Up @@ -276,17 +324,17 @@ listPackages sorted = do
then traverse_ echoT (fmt <$> inOrder (Map.assocs db))
else traverse_ echoT (fmt <$> Map.assocs db)
where
fmt :: (PackageName, PackageInfo) -> Text
fmt (name, PackageInfo{ version, repo }) =
runPackageName name <> " (" <> version <> ", " <> repo <> ")"

inOrder xs = fromNode . fromVertex <$> vs where
(gr, fromVertex) =
G.graphFromEdges' [ (pkg, name, dependencies pkg)
| (name, pkg) <- xs
]
vs = G.topSort (G.transposeG gr)
fromNode (pkg, name, _) = (name, pkg)
fmt :: (PackageName, PackageInfo) -> Text
fmt (name, PackageInfo{ version, repo }) =
runPackageName name <> " (" <> version <> ", " <> unRepo repo <> ")"

inOrder xs = fromNode . fromVertex <$> vs where
(gr, fromVertex) =
G.graphFromEdges' [ (pkg, name, dependencies pkg)
| (name, pkg) <- xs
]
vs = G.topSort (G.transposeG gr)
fromNode (pkg, name, _) = (name, pkg)

getSourcePaths :: PackageConfig -> PackageSet -> [PackageName] -> IO [Turtle.FilePath]
getSourcePaths PackageConfig{..} db pkgNames = do
Expand Down Expand Up @@ -455,7 +503,7 @@ main = do
commands = (Opts.subparser . fold)
[ Opts.command "init"
(Opts.info (initialize <$> optional ((,) <$> (fromString <$> set)
<*> optional (fromString <$> source))
<*> optional (Repo . fromString <$> source))
Opts.<**> Opts.helper)
(Opts.progDesc "Initialize a new package"))
, Opts.command "update"
Expand Down