diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,4 @@
+# Revision history for rpm-nvr
+
+## 0.1.0 (2021-06-06)
+- initial release with NV, NVR, NVRA, VerRel and rpmVerCompare
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+             GNU GENERAL PUBLIC LICENSE
+                Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                     Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+             GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                     NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+              END OF TERMS AND CONDITIONS
+
+     How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+# rpm-nvr
+
+Currently this library provides some types for RPM NVRs:
+NV, NVR, NVRA, VerRel with read and display functions.
+There is also an ordering of VerRel based on rpm's vercmp
+which is implemented as rpmVerCompare.
+
+See the haddock documentation for more details.
+
+The testsuite includes testcases for comparing rpm versions
+from the main rpm project.
diff --git a/rpm-nvr.cabal b/rpm-nvr.cabal
new file mode 100644
--- /dev/null
+++ b/rpm-nvr.cabal
@@ -0,0 +1,89 @@
+name:                rpm-nvr
+version:             0.1.0
+synopsis:            RPM package name-version-release datatypes
+description:
+            The library provides types related to RPM package
+            name-version-releases. There are modules for reading and showing:
+            .
+            * NV (name-version)
+            .
+            * NVR (name-version-release)
+            .
+            * NVRA (name-version-release.arch)
+            .
+            * VerRel (version-release)
+            .
+            * VerCmp for comparing RPM versions or releases
+            (used to order VerRel).
+license:             GPL-2
+license-file:        LICENSE
+author:              Jens Petersen <petersen@redhat.com>
+maintainer:          Jens Petersen <petersen@redhat.com>
+copyright:           2016-2018  Red Hat,
+                     2017,2021  Jens Petersen <petersen@redhat.com>
+category:            Distribution
+homepage:            https://github.com/juhp/rpm-nvr
+bug-reports:         https://github.com/juhp/rpm-nvr/issues
+build-type:          Simple
+extra-doc-files:     README.md
+                     ChangeLog.md
+cabal-version:       1.18
+tested-with:         GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5,
+                     GHC == 8.8.4, GHC == 8.10.4, GHC == 9.0.1
+
+source-repository head
+  type:                git
+  location:            https://github.com/juhp/rpm-nvr.git
+
+library
+  build-depends:       base < 5,
+                       extra >= 1.5.1
+  default-language:    Haskell2010
+  exposed-modules:     Data.RPM
+                       Data.RPM.NV
+                       Data.RPM.NVR
+                       Data.RPM.NVRA
+                       Data.RPM.VerCmp
+                       Data.RPM.VerRel
+  hs-source-dirs:      src
+
+  ghc-options:         -Wall
+  if impl(ghc >= 8.0)
+    ghc-options:       -Wcompat
+                       -Widentities
+                       -Wincomplete-uni-patterns
+                       -Wincomplete-record-updates
+                       -Wredundant-constraints
+  if impl(ghc >= 8.2)
+    ghc-options:       -fhide-source-paths
+  if impl(ghc >= 8.4)
+    ghc-options:       -Wmissing-export-lists
+                       -Wpartial-fields
+  if impl(ghc >= 8.10)
+    ghc-options:       -Wunused-packages
+
+test-suite tests
+    type:               exitcode-stdio-1.0
+    hs-source-dirs:     test
+    main-is:            Spec.hs
+    other-modules:      RPM.PackageSpec
+                        RPM.VersionSpec
+    build-depends:      hspec,
+                        base < 5.0,
+                        rpm-nvr
+
+    default-language:   Haskell2010
+
+    ghc-options:        -Wall
+  if impl(ghc >= 8.0)
+    ghc-options:       -Wcompat
+                       -Widentities
+                       -Wincomplete-uni-patterns
+                       -Wincomplete-record-updates
+                       -Wredundant-constraints
+  if impl(ghc >= 8.2)
+    ghc-options:       -fhide-source-paths
+  if impl(ghc >= 8.4)
+    ghc-options:       -Wpartial-fields
+  if impl(ghc >= 8.10)
+    ghc-options:       -Wunused-packages
diff --git a/src/Data/RPM.hs b/src/Data/RPM.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/RPM.hs
@@ -0,0 +1,36 @@
+-- | This module re-exports most of the submodules.
+module Data.RPM (
+  module Data.RPM.NV,
+  module Data.RPM.NVR,
+  module Data.RPM.NVRA,
+  module Data.RPM.VerRel,
+  module Data.RPM.VerCmp,
+  dropRelease,
+  addRelease,
+  dropArch,
+  addArch
+  ) where
+
+import Data.RPM.NV
+import Data.RPM.NVR
+import Data.RPM.NVRA
+import Data.RPM.VerCmp
+import Data.RPM.VerRel
+
+-- | Map a name-version-release into a name-version
+dropRelease :: NVR -> NV
+dropRelease (NVR n (VerRel v _)) = NV n v
+
+-- | Add a release to name-version to make an NVR
+addRelease :: NV -> String -> NVR
+addRelease _ "" = error "addRelease: release cannot be empty"
+addRelease (NV n v) r = NVR n (VerRel v r)
+
+-- | Map a name-version-release.arch into a name-version-release
+dropArch :: NVRA -> NVR
+dropArch (NVRA n vr _) = NVR n vr
+
+-- | Add an arch to NVR to make an NVRA
+addArch :: NVR -> String -> NVRA
+addArch _ "" = error "addArch: arch cannot be empty"
+addArch (NVR n vr) a = NVRA n vr a
diff --git a/src/Data/RPM/NV.hs b/src/Data/RPM/NV.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/RPM/NV.hs
@@ -0,0 +1,47 @@
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 2 of the License, or
+-- (at your option) any later version.
+
+-- | An NV type contains the name and version of a package only.
+module Data.RPM.NV (
+  NV(..),
+  showNV,
+  readNV,
+  eitherNV,
+  maybeNV
+  )
+where
+
+import Data.Either.Extra
+import Data.List.Extra
+
+-- | Package name-version type
+data NV = NV {name :: String,
+              version :: String}
+  deriving (Eq)
+
+-- | Render an name-version
+showNV :: NV -> String
+showNV (NV nm ver) = nm ++ "-" ++ ver
+
+-- | Read a package name-version or return a failure string
+eitherNV :: String -> Either String NV
+eitherNV "" = Left "NV cannot be empty string"
+eitherNV s =
+  case stripInfixEnd "-" s of
+    Just (n,v) ->
+      if null n || null v
+      then Left $ "NV must not start or end with '-': " ++ s
+      else Right (NV n v)
+    Nothing -> Left $ "NV must contain '-': " ++ s
+
+-- | Maybe read an package name-version
+maybeNV :: String -> Maybe NV
+maybeNV = eitherToMaybe . eitherNV
+
+-- | Read an NV
+--
+-- Errors if not of the form "name-version"
+readNV :: String -> NV
+readNV =  either error id . eitherNV
diff --git a/src/Data/RPM/NVR.hs b/src/Data/RPM/NVR.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/RPM/NVR.hs
@@ -0,0 +1,49 @@
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 2 of the License, or
+-- (at your option) any later version.
+
+-- | A type for name-version-release of an RPM package
+module Data.RPM.NVR (
+  NVR(..),
+  showNVR,
+  readNVR,
+  eitherNVR,
+  maybeNVR,
+  VerRel(..),
+  )
+where
+
+import Data.Either.Extra
+import Data.List.Extra
+
+import Data.RPM.VerRel
+
+-- | An rpm package name-version-release
+data NVR = NVR String VerRel
+  deriving Eq
+
+-- | render an name-version-release
+showNVR :: NVR -> String
+showNVR (NVR nm verrel) = nm ++ "-" ++ showVerRel verrel
+
+-- | Either read a package name-version-release or return a failure string
+eitherNVR :: String -> Either String NVR
+eitherNVR s =
+  case reverse (splitOn "-" s) of
+    rel:ver:emaN ->
+      if any null (rel:ver:emaN)
+      then Left $ "NVR cannot start or end with '-'s: " ++ s
+      else Right (NVR (intercalate "-" $ reverse emaN) (VerRel ver rel))
+    _ ->
+      Left $ "malformed NVR string: '" ++ s ++ "'"
+
+-- | Maybe read a package name-version-release string
+maybeNVR :: String -> Maybe NVR
+maybeNVR = eitherToMaybe . eitherNVR
+
+-- | read an NVR
+--
+-- Errors if not of the form "name-version-release"
+readNVR :: String -> NVR
+readNVR = either error id . eitherNVR
diff --git a/src/Data/RPM/NVRA.hs b/src/Data/RPM/NVRA.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/RPM/NVRA.hs
@@ -0,0 +1,78 @@
+{-# LANGUAGE CPP #-}
+
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 2 of the License, or
+-- (at your option) any later version.
+
+-- | A type for a (binary) RPM package.
+module Data.RPM.NVRA (
+  NVRA(..),
+  readNVRA,
+  eitherNVRA,
+  maybeNVRA,
+  showNVRA,
+  showPkgIdent,
+  showPkgVerRel,
+  )
+where
+
+import Data.Either.Extra
+import Data.List.Extra
+import Data.Maybe
+#if !MIN_VERSION_base(4,11,0)
+import Data.Monoid ((<>))
+#endif
+import Data.RPM.NVR
+import Data.RPM.VerRel
+
+-- | RPM package with name, version-release, and architecture
+--
+-- If arch is not needed use NVR instead.
+--
+-- FIXME: add epoch field
+data NVRA = NVRA {rpmName :: String,
+                  rpmVerRel :: VerRel,
+                  rpmArch :: String}
+  deriving (Eq, Ord)
+
+-- | Render an RpmPackage
+showNVRA :: NVRA -> String
+showNVRA (NVRA n vr a) = n <> "-" <> showVerRel vr <> "." <> a
+
+-- | Either read a name-version-release.arch or return a failure string
+eitherNVRA :: String -> Either String NVRA
+eitherNVRA "" = Left "NVRA string cannot be empty"
+eitherNVRA s@('-':_) = Left $ "NVRA cannot start with '-': " ++ s
+eitherNVRA s =
+  let nvra = fromMaybe s $ stripSuffix ".rpm" s
+  in
+    case reverse (splitOn "-" nvra) of
+      ps@(relarch:ver:emaN) ->
+        if any null ps
+        then Left $ "Bad NVRA string: " ++ s
+        else
+          case breakOnEnd "." relarch of
+            ("",_) -> Left $ "No arch suffix for " ++ s
+            (_,"") -> Left $ "Package release should not end in '.' " ++ s
+            (reldot,arch) ->
+              Right $ NVRA (intercalate "-" $ reverse emaN) (VerRel ver (dropEnd 1 reldot)) arch
+      _ -> Left $ "NVRA string must have form 'name-version-release.arch': " ++ s
+
+-- | Maybe read an NVRA
+maybeNVRA :: String -> Maybe NVRA
+maybeNVRA = eitherToMaybe . eitherNVRA
+
+-- | Parse an NVRA with arch suffix
+--
+-- Errors if not of the form "name-version-release[.arch]"
+readNVRA :: String -> NVRA
+readNVRA = either error id . eitherNVRA
+
+-- | Render the version-release of an NVRA
+showPkgVerRel :: NVRA -> String
+showPkgVerRel = showVerRel . rpmVerRel
+
+-- | Identifier for an RPM package identified by name and arch
+showPkgIdent :: NVRA -> String
+showPkgIdent p = rpmName p <> "." <> rpmArch p
diff --git a/src/Data/RPM/VerCmp.hs b/src/Data/RPM/VerCmp.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/RPM/VerCmp.hs
@@ -0,0 +1,83 @@
+{-# LANGUAGE CPP, MultiWayIf #-}
+
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 2 of the License, or
+-- (at your option) any later version.
+
+-- String-ified version of vercmp from codec-rpm-0.2.2/Codec/RPM/Version.hs
+-- Copyright 2016-2018 Red Hat
+-- Copyright 2021 Jens Petersen
+
+-- | Compare versions or releases using rpm's vercmp algorithm
+module Data.RPM.VerCmp (rpmVerCompare)
+where
+
+import Data.Char (isAsciiLower, isAsciiUpper, isDigit)
+import Data.List (isPrefixOf)
+#if !MIN_VERSION_base(4,11,0)
+import Data.Monoid ((<>))
+#endif
+
+-- | Compare two version numbers and return an 'Ordering'.
+--
+-- Native implementation of rpm's C vercmp
+rpmVerCompare :: String -> String -> Ordering
+rpmVerCompare a b =
+  if a == b then EQ
+    else let
+    -- strip out all non-version characters
+    -- keep in mind the strings may be empty after this
+    a' = dropSeparators a
+    b' = dropSeparators b
+
+    -- rpm compares strings by digit and non-digit components, so grab the first
+    -- component of one type
+    fn = if isDigit (head a') then isDigit else isAsciiAlpha
+    (prefixA, suffixA) = span fn a'
+    (prefixB, suffixB) = span fn b'
+ in
+    if | a' == b'                                       -> EQ
+       -- Nothing left means the versions are equal
+       {- null a' && null b'                             -> EQ -}
+       -- tilde is less than everything, including an empty string
+       | ("~" `isPrefixOf` a') && ("~" `isPrefixOf` b') -> rpmVerCompare (tail a') (tail b')
+       | ("~" `isPrefixOf` a')                            -> LT
+       | ("~" `isPrefixOf` b')                            -> GT
+       -- caret is more than everything, except .
+       | ("^" `isPrefixOf` a') && ("^" `isPrefixOf` b') -> rpmVerCompare (tail a') (tail b')
+       | ("^" `isPrefixOf` a') && null b'               -> GT
+       | null a' && ("^" `isPrefixOf` b')               -> LT
+       | ("^" `isPrefixOf` a')                            -> LT
+       | ("^" `isPrefixOf` b')                            -> GT
+       -- otherwise, if one of the strings is null, the other is greater
+       | (null a')                                        -> LT
+       | (null b')                                        -> GT
+       -- Now we have two non-null strings, starting with a non-tilde version character
+       -- If one prefix is a number and the other is a string, the one that is a number
+       -- is greater.
+       | isDigit (head a') && (not . isDigit) (head b') -> GT
+       | (not . isDigit) (head a') && isDigit (head b') -> LT
+       | isDigit (head a')                                -> (prefixA `compareAsInts` prefixB) <> (suffixA `rpmVerCompare` suffixB)
+       | otherwise                                          -> (prefixA `compare` prefixB) <> (suffixA `rpmVerCompare` suffixB)
+ where
+    compareAsInts :: String -> String -> Ordering
+    -- the version numbers can overflow Int, so strip leading 0's and do a string compare,
+    -- longest string wins
+    compareAsInts x y =
+        let x' = dropWhile (== '0') x
+            y' = dropWhile (== '0') y
+        in
+            if length x' > length y' then GT
+            else x' `compare` y'
+
+    -- isAlpha returns any unicode alpha, but we just want ASCII characters
+    isAsciiAlpha :: Char -> Bool
+    isAsciiAlpha x = isAsciiLower x || isAsciiUpper x
+
+    -- RPM only cares about ascii digits, ascii alpha, and ~ ^
+    isVersionChar :: Char -> Bool
+    isVersionChar x = isDigit x || isAsciiAlpha x || x == '~' || x == '^'
+
+    dropSeparators :: String -> String
+    dropSeparators = dropWhile (not . isVersionChar)
diff --git a/src/Data/RPM/VerRel.hs b/src/Data/RPM/VerRel.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/RPM/VerRel.hs
@@ -0,0 +1,59 @@
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 2 of the License, or
+-- (at your option) any later version.
+
+-- | A type for carrying the version and release of an rpm package.
+module Data.RPM.VerRel (
+  VerRel(..),
+  showVerRel,
+  readVerRel,
+  eitherVerRel,
+  maybeVerRel
+  )
+where
+
+import Data.Either.Extra
+import Data.List.Extra
+
+import Data.RPM.VerCmp
+
+-- | The version-release of an (rpm) package, which is ordered by
+-- @rpmVerCompare@ for version and release.
+--
+-- FIXME: note currently rpmVerCompare is not used for Eq (like codec-rpm).
+data VerRel = VerRel String String
+  deriving (Eq)
+
+-- | Display a VerRel
+showVerRel :: VerRel -> String
+showVerRel (VerRel ver rel) = ver ++ "-" ++ rel
+
+-- | Either read a package version-release or return an failure string
+eitherVerRel :: String -> Either String VerRel
+eitherVerRel s =
+  case stripInfixEnd "-" s of
+    Just (v,r) ->
+      if null v || null r
+      then error $ "VerRel cannot start or end with '-': " ++ s
+      else Right (VerRel v r)
+    Nothing -> Left $ "VerRel must contain a '-': " ++ s
+
+-- | Maybe read a package version-release
+maybeVerRel :: String -> Maybe VerRel
+maybeVerRel = eitherToMaybe . eitherVerRel
+
+-- | Read a version-release
+--
+-- Errors if malformed
+readVerRel :: String -> VerRel
+readVerRel = either error id . eitherVerRel
+
+instance Ord VerRel where
+  compare (VerRel v1 r1) (VerRel v2 r2) =
+    case rpmVerCompare v1 v2 of
+      EQ -> rpmVerCompare r1 r2
+      o -> o
+
+-- appendRelease :: VerRel -> String -> VerRel
+-- appendRelease (VerRel v r) d = VerRel v (r ++ d)
diff --git a/test/RPM/PackageSpec.hs b/test/RPM/PackageSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/RPM/PackageSpec.hs
@@ -0,0 +1,25 @@
+module RPM.PackageSpec (pkgspec) where
+
+import           Test.Hspec
+import           Data.Foldable(forM_)
+import           Data.RPM.NV
+import           Data.RPM.NVR
+import           Data.RPM.NVRA
+
+-- FIXME add some failures too
+pkgspec :: Spec
+pkgspec = do
+  describe "NV" $ do
+    forM_ ["my-pkg-1.0.1"] $ \nv ->
+      it nv $
+      showNV (readNV nv) `shouldBe` nv
+
+  describe "NVR" $ do
+    forM_ ["my-pkg-1.0.1-1.2"] $ \nvr ->
+      it nvr $
+      showNVR (readNVR nvr) `shouldBe` nvr
+
+  describe "NVRA" $ do
+    forM_ ["my-pkg-1.0.1-1.2.x86_64"] $ \pkg ->
+      it pkg $
+      showNVRA (readNVRA pkg) `shouldBe` pkg
diff --git a/test/RPM/VersionSpec.hs b/test/RPM/VersionSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/RPM/VersionSpec.hs
@@ -0,0 +1,136 @@
+-- taken from codec-rpm-0.2.2 and based on rpm/tests/rpmvercmp.at
+-- Copyright 2017-2018 Red Hat
+
+module RPM.VersionSpec (verspec) where
+
+import           Test.Hspec
+import           Data.Foldable(forM_)
+import           Data.RPM.VerCmp
+
+-- can also call rpm vercmp():
+-- `rpm --eval '%{lua: print(rpm.vercmp("$1", "$2"))}'`
+
+verspec :: Spec
+verspec = do
+    describe "Data.RPM.Version.vercmp" $ do
+        let vercmpCases = [
+                         ("1.0", "1.0", EQ),
+                         ("1.0", "2.0", LT),
+                         ("2.0", "1.0", GT),
+
+                         ("2.0.1", "2.0.1", EQ),
+                         ("2.0", "2.0.1", LT),
+                         ("2.0.1", "2.0", GT),
+
+                         ("2.0.1a", "2.0.1a", EQ),
+                         ("2.0.1a", "2.0.1", GT),
+                         ("2.0.1", "2.0.1a", LT),
+
+                         ("5.5p1", "5.5p1", EQ),
+                         ("5.5p1", "5.5p2", LT),
+                         ("5.5p2", "5.5p1", GT),
+
+                         ("5.5p10", "5.5p10", EQ),
+                         ("5.5p1", "5.5p10", LT),
+                         ("5.5p10", "5.5p1", GT),
+
+                         ("10xyz", "10.1xyz", LT),
+                         ("10.1xyz", "10xyz", GT),
+
+                         ("xyz10", "xyz10", EQ),
+                         ("xyz10", "xyz10.1", LT),
+                         ("xyz10.1", "xyz10", GT),
+
+                         ("xyz.4", "xyz.4", EQ),
+                         ("xyz.4", "8", LT),
+                         ("8", "xyz.4", GT),
+                         ("xyz.4", "2", LT),
+                         ("2", "xyz.4", GT),
+
+                         ("5.5p2", "5.6p1", LT),
+                         ("5.6p1", "5.5p2", GT),
+
+                         ("5.6p1", "6.5p1", LT),
+                         ("6.5p1", "5.6p1", GT),
+
+                         ("6.0.rc1", "6.0", GT),
+                         ("6.0", "6.0.rc1", LT),
+
+                         ("10b2", "10a1", GT),
+                         ("10a2", "10b2", LT),
+
+                         ("1.0aa", "1.0aa", EQ),
+                         ("1.0a", "1.0aa", LT),
+                         ("1.0aa", "1.0a", GT),
+
+                         ("10.0001", "10.0001", EQ),
+                         ("10.0001", "10.1", EQ),
+                         ("10.1", "10.0001", EQ),
+                         ("10.0001", "10.0039", LT),
+                         ("10.0039", "10.0001", GT),
+
+                         ("4.999.9", "5.0", LT),
+                         ("5.0", "4.999.9", GT),
+
+                         ("20101121", "20101121", EQ),
+                         ("20101121", "20101122", LT),
+                         ("20101122", "20101121", GT),
+
+                         ("2_0", "2_0", EQ),
+                         ("2.0", "2_0", EQ),
+                         ("2_0", "2.0", EQ),
+
+                         -- RhBug:178798 case
+                         ("a", "a", EQ),
+                         ("a+", "a+", EQ),
+                         ("a+", "a_", EQ),
+                         ("a_", "a+", EQ),
+                         ("+a", "+a", EQ),
+                         ("+a", "_a", EQ),
+                         ("_a", "+a", EQ),
+                         ("+_", "+_", EQ),
+                         ("_+", "+_", EQ),
+                         ("_+", "_+", EQ),
+                         ("+", "_", EQ),
+                         ("_", "+", EQ),
+
+                         -- Basic testcases for tilde sorting
+                         ("1.0~rc1", "1.0~rc1", EQ),
+                         ("1.0~rc1", "1.0", LT),
+                         ("1.0", "1.0~rc1", GT),
+                         ("1.0~rc1", "1.0~rc2", LT),
+                         ("1.0~rc2", "1.0~rc1", GT),
+                         ("1.0~rc1~git123", "1.0~rc1~git123", EQ),
+                         ("1.0~rc1~git123", "1.0~rc1", LT),
+                         ("1.0~rc1", "1.0~rc1~git123", GT),
+
+                         -- Basic testcases for caret sorting
+                         ("1.0^", "1.0^", EQ),
+                         ("1.0^", "1.0", GT),
+                         ("1.0", "1.0^", LT),
+                         ("1.0^git1", "1.0^git1", EQ),
+                         ("1.0^git1", "1.0", GT),
+                         ("1.0", "1.0^git1", LT),
+                         ("1.0^git1", "1.0^git2", LT),
+                         ("1.0^git2", "1.0^git1", GT),
+                         ("1.0^git1", "1.01", LT),
+                         ("1.01", "1.0^git1", GT),
+                         ("1.0^20160101", "1.0^20160101", EQ),
+                         ("1.0^20160101", "1.0.1", LT),
+                         ("1.0.1", "1.0^20160101", GT),
+                         ("1.0^20160101^git1", "1.0^20160101^git1", EQ),
+                         ("1.0^20160102", "1.0^20160101^git1", GT),
+                         ("1.0^20160101^git1", "1.0^20160102", LT),
+
+                         -- Basic testcases for tilde and caret sorting
+                         ("1.0~rc1^git1", "1.0~rc1^git1", EQ),
+                         ("1.0~rc1^git1", "1.0~rc1", GT),
+                         ("1.0~rc1", "1.0~rc1^git1", LT),
+                         ("1.0^git1~pre", "1.0^git1~pre", EQ),
+                         ("1.0^git1", "1.0^git1~pre", GT),
+                         ("1.0^git1~pre", "1.0^git1", LT)
+                         ]
+
+        forM_ vercmpCases $ \(verA, verB, ord) ->
+          it (verA ++ " " ++ show ord ++ " " ++ verB) $
+          rpmVerCompare verA verB `shouldBe` ord
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,8 @@
+import Test.Hspec
+
+import RPM.PackageSpec
+import RPM.VersionSpec
+
+main :: IO ()
+main =
+  mapM_ hspec [verspec,pkgspec]
