Upgrading to Yesod 1.6

By James Parker - October 13, 2018

Yesod 1.6 was released in February of this year. This new version changes how subsites are implemented, which breaks most Yesod web applications. I did not find many good resources describing how to migrate web applications to 1.6, so I am documenting the steps I took to fix my website. It was not immediately obvious what changes needed to be made, so hopefully this is helpful for others.

My website was originally running on Stackage LTS 9.2, so I upgraded to LTS 12.2 by updating my stack.yaml file:

resolver: lts-12.2

I recompiled the website with stack build, but this gave me version conflicts for dependencies. I fixed the version conflicts for dependencies in my <package>.cabal file by widening the accepted versions in the build-depends section (I actually just got rid of most of the version constraints). If you use hpack, you'll need to edit the dependencies in your package.yaml file.

After fixing the version conflicts, I started getting compiler errors:

src/Foundation.hs:94:5: error:
        ‘shouldLog’ is not a (visible) method of class ‘Yesod’
       |
    94 |     shouldLog app _source level =
       |     ^^^^^^^^^

It looks like shouldLog has been removed. I just commented it out in src/Foundation.hs.

Then I got the following errors:

src/Foundation.hs:147:9: error:
        • Couldn't match kind ‘* -> *’ with ‘*’
          When matching types
            m0 :: * -> *
            site0 :: *
          Expected type: YesodDB site0 (AuthenticationResult App)
            Actual type: ReaderT
                           (YesodPersistBackend site0) (t0 m0) (AuthenticationResult App)
        • In a stmt of a 'do' block:
            x <- getBy $ UniqueUser $ credsIdent creds
          In the second argument of ‘($)’, namely
            ‘do x <- getBy $ UniqueUser $ credsIdent creds
                case x of
                  Just (Entity uid _) -> return $ Authenticated uid
                  Nothing -> do ...’
          In the expression:
            runDB
              $ do x <- getBy $ UniqueUser $ credsIdent creds
                   case x of
                     Just (Entity uid _) -> return $ Authenticated uid
                     Nothing -> do ...
        |
    147 |         x <- getBy $ UniqueUser $ credsIdent creds
        |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

It looks like the authentication API has changed. This makes sense since authentication is implemented in a subsite, and we know subsites have been updated. To fix this, I call liftHandler outside of runDB to run database operations in the site's root monad:

authenticate creds = liftHandler $ runDB $ do

I also run an IO computation inside the database computation, so I eliminated one lift:

-- Original
now <- lift $ lift getCurrentTime

-- Fixed
now <- lift getCurrentTime

When attempting to compile again, I received the following error:

src/Foundation.hs:160:23: error:
        • Could not deduce: m ~ (->) a0
          from the context: (MonadHandler m, HandlerSite m ~ App)
            bound by the type signature for:
                       authHttpManager :: forall (m :: * -> *).
                                          (MonadHandler m, HandlerSite m ~ App) =>
                                          m Manager
            at src/Foundation.hs:160:5-19
          ‘m’ is a rigid type variable bound by
            the type signature for:
              authHttpManager :: forall (m :: * -> *).
                                 (MonadHandler m, HandlerSite m ~ App) =>
                                 m Manager
            at src/Foundation.hs:160:5-19
          Expected type: m Manager
            Actual type: a0 -> Manager
        • In the expression: getHttpManager
          In an equation for ‘authHttpManager’:
              authHttpManager = getHttpManager
          In the instance declaration for ‘YesodAuth App’
        • Relevant bindings include
            authHttpManager :: m Manager (bound at src/Foundation.hs:160:5)
        |
    160 |     authHttpManager = getHttpManager
        |                       ^^^^^^^^^^^^^^

It looks like some part of the HTTP manager API changed. authHttpManager has a default implementation though, so I just commented it out. You may need to implement this if you are not using the default global manager.

The following is the last error I encountered:

app/DevelMain.hs:40:1: error:
        Could not find module ‘Foreign.Store’
        Perhaps you meant
          Foreign.Ptr (from base-4.11.1.0)
          Foreign.Safe (from base-4.11.1.0)
          Foreign.Storable (from base-4.11.1.0)
        Use -v to see a list of the files searched for.
       |
    40 | import Foreign.Store
       | ^^^^^^^^^^^^^^^^^^^^

Module Foreign.Store is in a separate package now, so I added its package, foreign-store to my cabal file.

Once I knew what to do, upgrading was relatively painless. If you would like to learn more about the changes in Yesod 1.6, check out Michael Snoyman's blog post.