hkgr 0.3 → 0.4
raw patch · 5 files changed
+134/−76 lines, 5 files
Files
- CHANGELOG.md +9/−0
- README.md +18/−11
- data/template.cabal.tmpl +7/−4
- hkgr.cabal +7/−7
- src/Main.hs +93/−54
CHANGELOG.md view
@@ -1,5 +1,14 @@ # Changelog +## 0.4 (2022-03-27)+- template.cabal: use cabal 2.0 and define hs-source-dirs+- 'tagdist --existing-tag' replaces 'dist' command+- 'upload': add --existing-tag for dist rather than tagdist+- 'new': stack init with lts-17+- 'rename': experimental command to rename a project+- add '--no-hlint' option to skip running hlint+ (also hlint is no longer run for 'publish')+ ## 0.3 (2020-05-07) - only read Hackage username/password if not in ~/.cabal/config - 'upload': show newer untagged commits
README.md view
@@ -7,7 +7,7 @@ `hkgr` (pronounced "hackager") is a tool for making releases of Haskell packages on Hackage. -It uses a cautious stepped approach to releases.+It uses a cautious stepped iterative approach to releases. ## Example usage @@ -46,8 +46,8 @@ Source tarball created: dist/hkgr-0.2.5.tar.gz ``` -Alternatively if you have manually tagged the version-with `vX.Y.Z` you can use `hkgr dist` to create a tarball.+Alternatively if you had manually tagged the release with `v0.2.5`+you can use `hkgr tagdist --existing-tag` to create a dist tarball. The tarball can now be uploaded to Hackage as a candidate release: ```@@ -57,9 +57,14 @@ ``` One can continue to `tagdist -f` and `upload` until-everything looks good and CI passed etc,-then it is time to push the final tag and publish the release:+everything looks good and CI passed etc. +Note that `tagdist -f` and `upload`, can be combined as `upload -f`.+If one wants to check the candidate upload quickly,+it is faster just to run `upload` followed by `upload -f` if needed.++Then it is time to push the final tag and publish the release:+ ``` $ hkgr publish Everything up-to-date@@ -96,16 +101,19 @@ If sdist fails for some reason then hkgr tries to reset the tag. -### dist Alternatively if you have already manually tagged a release with 'v' prefix-you can use `hkgr dist` to create a tarball.+you can use `--existing-tag` to create a dist tarball. ### upload `hkgr upload` uploads the tarball to Hackage as a candidate release.-Like `hkgr tagdist -f`, this can be repeated.+Like `hkgr tagdist -f`, `hkgr upload -f` can be repeated. -Haddock draft documentation can also be uploaded if desired with `hkgr upload-haddock`.+Haddock draft documentation can also be uploaded once if desired+with `hkgr upload-haddock`. +If you have an existing version tag (starting with `v`) you can use+the `--existing-tag` option to skip the tagging step (like for `tagdist`).+ ### publish `hkgr publish` releases the tarball to Hackage. @@ -127,8 +135,7 @@ A `stack.yaml` file and git repo is also set up. -One can use `hub create` etc to create the project on Github.+One can use `gh repo create` etc to create the project repo on Github. ## Requirements- hkgr uses `cabal-install` >=2, `git`, and also `hlint` if available.
data/template.cabal.tmpl view
@@ -14,17 +14,20 @@ build-type: Simple -- extra-doc-files: README.md -- ChangeLog.md-cabal-version: 1.18---tested-with: GHC == 8.4.4, GHC == 8.6.5, GHC == 8.8.4, GHC == 8.10.4+cabal-version: 2.0+-- tested-with: GHC == 8.6.5+-- | == 8.8.4+-- | == 8.10.7+-- | == 9.0.2 source-repository head type: git location: https://github.com/@USER@/@PROJECT@.git executable @PROJECT@- main-is: src/Main.hs+ main-is: Main.hs -- other-modules: Paths_@PROJECT_@--- hs-source-dirs: app+ hs-source-dirs: src build-depends: base < 5, @PROJECT@ default-language: Haskell2010
hkgr.cabal view
@@ -1,9 +1,9 @@ cabal-version: 2.0 name: hkgr-version: 0.3+version: 0.4 synopsis: Simple Hackage release workflow for package maintainers description:- Hkgr (pronounced "Hackager") is a tool to help make new releases of+ Hkgr (Hackager) is a tool to help make new releases of Haskell packages, with commands for git tagging, pristine sdist, and uploading to Hackage. There is also a command for creating a new local project.@@ -11,17 +11,17 @@ bug-reports: https://github.com/juhp/hkgr/issues license: GPL-3 license-file: LICENSE-author: Jens Petersen-maintainer: juhpetersen@gmail.com-copyright: 2019-2021 Jens Petersen+author: Jens Petersen <juhpetersen@gmail.com>+maintainer: Jens Petersen <juhpetersen@gmail.com>+copyright: 2019-2022 Jens Petersen category: Util build-type: Simple data-dir: data data-files: template.cabal.tmpl extra-doc-files: README.md , CHANGELOG.md-tested-with: GHC == 8.8.4, GHC == 8.6.5, GHC == 8.4.4, GHC == 8.2.2,- GHC == 8.0.2+tested-with: GHC == 8.10.5, GHC == 8.8.4, GHC == 8.6.5, GHC == 8.4.4,+ GHC == 8.2.2, GHC == 8.0.2 source-repository head type: git
src/Main.hs view
@@ -33,25 +33,39 @@ "'Hackager' is a package release tool for easy Hackage workflow" $ subcommands [ Subcommand "new" "setup a new project" $- newCmd <$> optional (strArg "PROJECT")+ newCmd+ <$> optional (strArg "PROJECT") , Subcommand "tagdist" "'git tag' version and 'cabal sdist' tarball" $- tagDistCmd False <$> forceOpt "Move existing tag"- , Subcommand "dist" "'cabal sdist' tarball" $- tagDistCmd True <$> forceOpt "Recreate tarball"+ tagDistCmd+ <$> existingTag+ <*> forceOpt "Move existing tag"+ <*> hlintOpt , Subcommand "upload" "'cabal upload' candidate tarball to Hackage" $- uploadCmd False <$> forceOpt "Move existing tag"+ uploadCmd False+ <$> existingTag+ <*> forceOpt "Move existing tag"+ <*> hlintOpt , Subcommand "publish" "Publish to Hackage ('cabal upload --publish')" $- pure $ uploadCmd True False+ pure $ uploadCmd True False False True , Subcommand "upload-haddock" "Upload candidate documentation to Hackage" $ pure $ upHaddockCmd False , Subcommand "publish-haddock" "Publish documentation to Hackage" $ pure $ upHaddockCmd True , Subcommand "version" "Show the package version from .cabal file" $ pure showVersionCmd+ , Subcommand "rename" "Rename the Cabal package" $+ renameCmd+ <$> strArg "NEWNAME" ] where forceOpt = switchWith 'f' "force" + existingTag =+ switchWith 'T' "existing-tag" "Use existing tag to create tarball"++ hlintOpt =+ switchWith 'H' "no-hlint" "Skip running hlint"+ git :: String -> [String] -> P.ProcessConfig () () () git c args = P.proc "git" (c:args) @@ -100,26 +114,44 @@ error' = error #endif -tagDistCmd :: Bool -> Bool -> IO ()-tagDistCmd sdistOnly force = do+tarGzExt :: String+tarGzExt = "tar.gz"++assertTagOnBranch :: String -> IO ()+assertTagOnBranch tag =+ whenM (null <$> cmdOut (git "branch" ["--contains", "tags/" ++ tag])) $+ error' $ tag ++ " is no longer on branch: use --force to move it"++tagDistCmd :: Bool -> Bool -> Bool -> IO ()+tagDistCmd existingtag force noHlint = do pkgid <- checkPackage True let tag = pkgidTag pkgid- tagHash <- cmdMaybe (git "rev-parse" [tag])- if sdistOnly- then do- when (isNothing tagHash) $- error' $ "tag " ++ tag ++ " do not exist"- sdist force pkgid+ mtagHash <- cmdMaybe (git "rev-parse" [tag])+ when (not force && isJust mtagHash) $ assertTagOnBranch tag+ if existingtag+ then if isNothing mtagHash+ then error' $ "tag " ++ tag ++ " does not exist"+ else sdist force noHlint pkgid else do- when (isJust tagHash && not force) $- error' $ "tag " ++ tag ++ " exists: use --force to override and move"- git_ "tag" $ ["--force" | force] ++ [tag]- unless force $ putStrLn tag- sdist force pkgid `onException` do- putStrLn "Resetting tag"- if force- then git_ "tag" ["--force", tag, fromJust tagHash]- else git_ "tag" ["--delete", tag]+ if isJust mtagHash && not force+ then do+ headHash <- cmdOut (git "rev-parse" ["HEAD"])+ let onHead = Just headHash == mtagHash+ putStrLn $ "tag is " ++ (if onHead then "" else "not ") ++ "on HEAD"+ let tarball = sdistDir </> showPkgId pkgid <.> tarGzExt+ exists <- doesFileExist tarball+ if exists+ then error' $ "tag " ++ tag ++ " exists: use --force to " +++ (if exists then "override tarball and " else "") ++ "move"+ else sdist force noHlint pkgid+ else do+ git_ "tag" $ ["--force" | force] ++ [tag]+ unless force $ putStrLn tag+ sdist force noHlint pkgid `onException` do+ putStrLn "Resetting tag"+ if force+ then git_ "tag" ["--force", tag, fromJust mtagHash]+ else git_ "tag" ["--delete", tag] pkgidTag :: PackageIdentifier -> String pkgidTag pkgid = "v" ++ packageVersion pkgid@@ -149,20 +181,20 @@ checkNotPublished :: PackageIdentifier -> IO () checkNotPublished pkgid = do- let published = sdistDir </> showPkgId pkgid <.> ".tar.gz" <.> "published"+ let published = sdistDir </> showPkgId pkgid <.> tarGzExt <.> "published" exists <- doesFileExist published when exists $ error' $ showPkgId pkgid <> " was already published!!"- let oldpublished = "dist" </> showPkgId pkgid <.> ".tar.gz" <.> "published"+ let oldpublished = "dist" </> showPkgId pkgid <.> tarGzExt <.> "published" oldExists <- doesFileExist oldpublished when oldExists $ error' $ showPkgId pkgid <> " was already published!!" sdistDir :: FilePath sdistDir = ".hkgr" -sdist :: Bool -> PackageIdentifier -> IO ()-sdist force pkgid = do+sdist :: Bool -> Bool -> PackageIdentifier -> IO ()+sdist force noHlint pkgid = do let tag = pkgidTag pkgid- let target = sdistDir </> showPkgId pkgid <.> ".tar.gz"+ let target = sdistDir </> showPkgId pkgid <.> tarGzExt haveTarget <- doesFileExist target when haveTarget $ if force@@ -175,10 +207,11 @@ whenM (doesFileExist ".gitmodules") $ git_ "submodule" ["update", "--init"] cabal_ "check" []- mhlint <- findExecutable "hlint"- when (isJust mhlint) $ do- putStrLn "Running hlint"- void $ P.runProcess $ P.proc "hlint" ["--no-summary", "."]+ unless noHlint $ do+ mhlint <- findExecutable "hlint"+ when (isJust mhlint) $ do+ putStrLn "Running hlint"+ void $ P.runProcess $ P.proc "hlint" ["--no-summary", "."] let dest = takeDirectory $ cwd </> target unlessM (doesDirectoryExist dest) $ createDirectoryIfMissing True dest@@ -190,17 +223,16 @@ putStrLn $ packageVersion pkgid -- FIXME cabal install creates tarballs now-uploadCmd :: Bool -> Bool -> IO ()-uploadCmd publish force = do+uploadCmd :: Bool -> Bool -> Bool -> Bool -> IO ()+uploadCmd publish existingtag force noHlint = do needProgram "cabal" pkgid <- checkPackage False- let file = sdistDir </> showPkgId pkgid <.> ".tar.gz"+ let tarball = sdistDir </> showPkgId pkgid <.> tarGzExt tag = pkgidTag pkgid- exists <- doesFileExist file+ exists <- doesFileExist tarball when (force || not exists) $- tagDistCmd False force- whenM (null <$> cmdOut (git "branch" ["--contains", "tags/" ++ tag])) $- error' $ tag ++ " is no longer on branch: use --force to move it"+ tagDistCmd existingtag force noHlint+ assertTagOnBranch tag untagged <- cmdLines $ git "log" ["--pretty=reference", tag ++ "..HEAD"] unless (null untagged) $ do putStrLn "untagged newer commits:"@@ -215,10 +247,10 @@ putStrLn "done" git_ "push" ["origin", tag] userpassBS <- getUserPassword- void $ P.readProcessInterleaved_ (P.setStdin (P.byteStringInput userpassBS) $ P.proc "cabal" ("upload" : ["--publish" | publish] ++ [file]))+ void $ P.readProcessInterleaved_ (P.setStdin (P.byteStringInput userpassBS) $ P.proc "cabal" ("upload" : ["--publish" | publish] ++ [tarball])) putStrLn $ (if publish then "Published at " else "Uploaded to ") ++ "https://hackage.haskell.org/package/" ++ showPkgId pkgid ++ if publish then "" else "/candidate" when publish $- createFileLink (takeFileName file) (file <.> "published")+ createFileLink (takeFileName tarball) (tarball <.> "published") getUserPassword :: IO B.ByteString getUserPassword = do@@ -298,7 +330,7 @@ else do -- filter out dirs dirs <- filterM doesDirectoryExist files- if dirs == files then error' "Could not guess name"+ if dirs == files then error' "Could not guess name: subdirectories found" else case filter ("cabal" `isExtensionOf`) $ files \\ dirs of [] -> takeFileName <$> getCurrentDirectory@@ -329,7 +361,7 @@ mstack <- findExecutable "stack" -- FIXME add stack.yaml template too when (not haveStackCfg && isJust mstack) $ do- cmd_ "stack" ["init", "--verbosity", "warn", "--resolver", "lts-16"]+ cmd_ "stack" ["init", "--verbosity", "warn", "--resolver", "lts-19"] sed ["/^#/d", "/^$/d"] "stack.yaml" haveGit <- doesDirectoryExist ".git" unless haveGit $ do@@ -344,11 +376,6 @@ [cbl] -> return $ Just (takeBaseName cbl) _ -> error' "More than one .cabal file found!" - sed :: [String] -> FilePath -> IO ()- sed [] _ = error' "sed given no script"- sed args file =- cmd_ "sed" $ ["-i", "-e"] ++ intersperse "-e" args ++ [file]- setupCabalTemplate :: String -> IO () setupCabalTemplate name = do userTemplate <- getUserConfigFile "hkgr" "template.cabal"@@ -376,21 +403,33 @@ unlessM (doesFileExist modulePath) $ do createDirectoryIfMissing True $ takeDirectory modulePath writeFile modulePath "-- SPDX-License-Identifier: BSD-3-Clause\n\nmodule MyLib where\n"- -- sed ["-i", "1s/^/-- SPDX-License-Identifier: BSD-3-Clause\n\n/"] modulePath where- replaceHolder lbl val file =- sed ["s/@" ++ lbl ++ "@/" ++ val ++ "/"] file+ replaceHolder lbl val =+ sed ["s/@" ++ lbl ++ "@/" ++ val ++ "/"] underscore '-' = '_' underscore c = c +sed :: [String] -> FilePath -> IO ()+sed [] _ = error' "sed given no script"+sed args file =+ cmd_ "sed" $ ["-i", "-e"] ++ intersperse "-e" args ++ [file]+ #if !MIN_VERSION_filepath(1,4,2)- isExtensionOf :: String -> FilePath -> Bool- isExtensionOf ext@('.':_) = isSuffixOf ext . takeExtensions- isExtensionOf ext = isSuffixOf ('.':ext) . takeExtensions+isExtensionOf :: String -> FilePath -> Bool+isExtensionOf ext@('.':_) = isSuffixOf ext . takeExtensions+isExtensionOf ext = isSuffixOf ('.':ext) . takeExtensions #endif #if !MIN_VERSION_extra(1,6,15) fromMaybeM :: Monad m => m a -> m (Maybe a) -> m a fromMaybeM n = maybeM n return #endif++renameCmd :: String -> IO ()+renameCmd newname = do+ pkgid <- getPackageId+ let oldname = unPackageName (pkgName pkgid)+ sed ["s/" ++ oldname ++ "/" ++ newname ++ "/g"] $ oldname <.> "cabal"+ renameFile (oldname <.> "cabal") (newname <.> "cabal")+ -- FIXME adjust README