Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Add package to library component in package.yaml #1237

Merged
merged 11 commits into from
May 18, 2019
3 changes: 2 additions & 1 deletion haskell-ide-engine.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,11 @@ test-suite unit-test
DiffSpec
ExtensibleStateSpec
GhcModPluginSpec
LiquidSpec
HaRePluginSpec
HooglePluginSpec
JsonSpec
LiquidSpec
PackagePluginSpec
Spec
-- Technically cabal-helper should be a 'run-tool-depends', but that doesn't exist yet
build-tool-depends: cabal-helper:cabal-helper-main, hspec-discover:hspec-discover
Expand Down
58 changes: 42 additions & 16 deletions src/Haskell/Ide/Engine/Plugin/Package.hs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ data AddParams = AddParams
{ rootDirParam :: FilePath -- ^ The root directory.
, fileParam :: ModulePath -- ^ A path to a module inside the
-- library/executable/test-suite you want to
-- add the package to. May be a realtive oir
-- add the package to. May be a relative or
-- absolute path, thus, must be normalised.
, packageParam :: Package -- ^ The name of the package to add.
}
Expand All @@ -76,7 +76,7 @@ data AddParams = AddParams
-- | FilePath to a cabal package description file.
type CabalFilePath = FilePath
-- | FilePath to a package.yaml package description file.
type PackageYamlFilePath = FilePath
type HpackFilePath = FilePath
-- | FilePath to a module within the project.
-- May be used to establish what component the dependency shall be added to.
type ModulePath = FilePath
Expand All @@ -88,8 +88,14 @@ type Package = T.Text
-- Supported are `*.cabal` and `package.yaml` specifications.
-- Moreover, may fail with an IOException in case of a filesystem problem.
addCmd :: CommandFunc AddParams J.WorkspaceEdit
addCmd = CmdSync $ \(AddParams rootDir modulePath pkg) -> do
addCmd = CmdSync addCmd'

-- | Add a package to the project's dependencies.
-- May fail if no project dependency specification can be found.
-- Supported are `*.cabal` and `package.yaml` specifications.
-- Moreover, may fail with an IOException in case of a filesystem problem.
addCmd' :: AddParams -> IdeGhcM (IdeResult J.WorkspaceEdit)
addCmd' (AddParams rootDir modulePath pkg) = do
packageType <- liftIO $ findPackageType rootDir
fileMap <- GM.mkRevRedirMapFunc

Expand All @@ -105,9 +111,10 @@ addCmd = CmdSync $ \(AddParams rootDir modulePath pkg) -> do
liftToGhc $ editHpackPackage absFp relModulePath pkg
NoPackage -> return $ IdeResultFail (IdeError PluginError "No package.yaml or .cabal found" Null)

data PackageType = CabalPackage FilePath -- ^ Location of Cabal File.
| HpackPackage FilePath -- ^ Location of `package.yaml`
data PackageType = CabalPackage FilePath -- ^ Location of Cabal File. May be relative.
| HpackPackage FilePath -- ^ Location of `package.yaml`. May be relative.
| NoPackage -- ^ No package format has been found.
deriving (Show, Eq)

-- | Find the package type the project with the given root uses.
-- Might have weird results if there is more than one cabal package specification
Expand All @@ -129,12 +136,13 @@ findPackageType rootDir = do
return $ fromMaybe NoPackage $ asum [HpackPackage <$> mHpack, CabalPackage <$> mCabal]

-- | Edit a hpack package to add the given package to the package.yaml.
-- If package.yaml is not in an expected format, will fail fatally.
--
-- Currently does not preserve format.
-- Keep an eye out on this other GSOC project!
-- https://github.com/wisn/format-preserving-yaml
editHpackPackage :: PackageYamlFilePath -- ^ Path to the package.yaml file
-- containing the package description.
editHpackPackage :: HpackFilePath -- ^ Path to the package.yaml file
-- containing the package description.
-> ModulePath -- ^ Path to the module where the command has
-- been issued in.
-- Used to find out what component the
Expand All @@ -148,19 +156,29 @@ editHpackPackage fp modulePath pkgName = do

case Y.decodeThrow contents :: Maybe Object of
Just obj -> do
-- Map over all major components, such as "executable", "executables",
-- "tests" and "benchmarks". Note, that "library" is a major component,
-- but its structure is different and can not be mapped over in the same way.
--
-- Only adds the package if the declared "source-dirs" field is part of the
-- module path, or if no "source-dirs" is declared.
let compsMapped = mapComponentTypes (ensureObject $ mapComponents (ensureObject $ mapCompDependencies addDep)) obj

let addDepToMainLib = fromMaybe True $ do
Object lib <- HM.lookup "library" compsMapped
sourceDirs <- HM.lookup "source-dirs" lib
return $ isInSourceDir sourceDirs
-- Is there a global "dependencies" yaml object?
let addDepToMainDep = fromMaybe False $ do
Array _ <- HM.lookup "dependencies" compsMapped
return True

let newPkg = if addDepToMainLib
then mapMainDependencies addDep compsMapped
else compsMapped
-- Either add the package to only the top-level "dependencies",
-- or to all main components of which the given module is part of.
let newPkg
| addDepToMainDep = mapMainDependencies addDep obj
-- Map over the library component at last, since it has different structure.
| otherwise = mapLibraryDependency addDep compsMapped

newPkgText = T.decodeUtf8 $ Y.encode newPkg
let newPkgText = T.decodeUtf8 $ Y.encode newPkg

-- Construct the WorkSpaceEdit
let numOldLines = length $ T.lines $ T.decodeUtf8 contents
range = J.Range (J.Position 0 0) (J.Position numOldLines 0)
textEdit = J.TextEdit range newPkgText
Expand All @@ -179,10 +197,18 @@ editHpackPackage fp modulePath pkgName = do

mapMainDependencies :: (Value -> Value) -> Object -> Object
mapMainDependencies f o =
let g "dependencies" = f
let g :: T.Text -> Value -> Value
g "dependencies" = f
g _ = id
in HM.mapWithKey g o

mapLibraryDependency :: (Value -> Value) -> Object -> Object
mapLibraryDependency f o =
let g :: T.Text -> Value -> Value
g "library" (Y.Object o') = Y.Object (mapCompDependencies f o')
g _ x = x
in HM.mapWithKey g o

mapComponentTypes :: (Value -> Value) -> Object -> Object
mapComponentTypes f o =
let g "executables" = f
Expand Down
50 changes: 40 additions & 10 deletions test/functional/FunctionalCodeActionsSpec.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}

module FunctionalCodeActionsSpec where

Expand Down Expand Up @@ -177,7 +178,9 @@ spec = describe "code actions" $ do
]
]
describe "add package suggestions" $ do
it "adds to .cabal files" $ runSession hieCommand fullCaps "test/testdata/addPackageTest/cabal" $ do
-- Only execute this test with ghc 8.4.4, below seems to be broken in the package.
#if (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,4,0,0)))
it "adds to .cabal files" $ runSession hieCommand fullCaps "test/testdata/addPackageTest/cabal-exe" $ do
doc <- openDoc "AddPackage.hs" "haskell"

-- ignore the first empty hlint diagnostic publish
Expand All @@ -201,9 +204,9 @@ spec = describe "code actions" $ do

contents <- getDocumentEdit . TextDocumentIdentifier =<< getDocUri "add-package-test.cabal"
liftIO $ T.lines contents `shouldSatisfy` \x -> any (\l -> "text -any" `T.isSuffixOf` (x !! l)) [15, 16]

#endif
it "adds to hpack package.yaml files" $
runSession hieCommand fullCaps "test/testdata/addPackageTest/hpack" $ do
runSession hieCommand fullCaps "test/testdata/addPackageTest/hpack-exe" $ do
doc <- openDoc "app/Asdf.hs" "haskell"

-- ignore the first empty hlint diagnostic publish
Expand All @@ -227,9 +230,35 @@ spec = describe "code actions" $ do

contents <- getDocumentEdit . TextDocumentIdentifier =<< getDocUri "package.yaml"
liftIO $ do
T.lines contents !! 33 `shouldSatisfy` T.isSuffixOf "zlib"
T.lines contents !! 12 `shouldNotSatisfy` T.isSuffixOf "zlib"
T.lines contents !! 13 `shouldNotSatisfy` T.isSuffixOf "zlib"
T.lines contents !! 3 `shouldSatisfy` T.isSuffixOf "zlib"
T.lines contents !! 21 `shouldNotSatisfy` T.isSuffixOf "zlib"

it "adds to hpack package.yaml files if both are present" $
runSession hieCommand fullCaps "test/testdata/addPackageTest/hybrid-exe" $ do
doc <- openDoc "app/Asdf.hs" "haskell"

-- ignore the first empty hlint diagnostic publish
[_,diag:_] <- count 2 waitForDiagnostics

let preds = [ T.isPrefixOf "Could not load module ‘Codec.Compression.GZip’"
, T.isPrefixOf "Could not find module ‘Codec.Compression.GZip’"
]
in liftIO $ diag ^. L.message `shouldSatisfy` \x -> any (\f -> f x) preds

mActions <- getAllCodeActions doc
let allActions = map fromAction mActions
action = head allActions

liftIO $ do
action ^. L.title `shouldBe` "Add zlib as a dependency"
forM_ allActions $ \a -> a ^. L.kind `shouldBe` Just CodeActionQuickFix
forM_ allActions $ \a -> a ^. L.command . _Just . L.command `shouldSatisfy` T.isSuffixOf "package:add"

executeCodeAction action

contents <- getDocumentEdit . TextDocumentIdentifier =<< getDocUri "package.yaml"
liftIO $
T.lines contents !! 21 `shouldSatisfy` T.isSuffixOf "zlib"

-- -----------------------------------

Expand Down Expand Up @@ -316,10 +345,11 @@ spec = describe "code actions" $ do

contents <- documentContents doc

liftIO $ contents `shouldBe`
"module TypedHoles where\n\
\foo :: [Int] -> Int\n\
\foo x = " <> suggestion
liftIO $ contents `shouldBe` T.concat
[ "module TypedHoles where\n"
, "foo :: [Int] -> Int\n"
, "foo x = " <> suggestion
]

it "shows more suggestions" $
runSession hieCommand fullCaps "test/testdata" $ do
Expand Down
14 changes: 14 additions & 0 deletions test/testdata/addPackageTest/cabal-exe/add-package-test.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: add-package-test
version: 0.1.0.0
license: BSD3
author: Luke Lau
maintainer: [email protected]
build-type: Simple
extra-source-files: ChangeLog.md
cabal-version: >=1.10

executable AddPackage
exposed-modules: ./.
main-is: AddPackage.hs
build-depends: base >=4.7 && <5
default-language: Haskell2010
4 changes: 4 additions & 0 deletions test/testdata/addPackageTest/cabal-lib/AddPackage.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module AddPackage where

import Data.Text
foo = pack "I'm a Text"
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ description: Please see the README on GitHub at <https://github.com/gith
dependencies:
- base >= 4.7 && < 5

library:
source-dirs: src

executables:
asdf-exe:
main: Main.hs
Expand All @@ -34,15 +31,4 @@ executables:
- -rtsopts
- -with-rtsopts=-N
dependencies:
- asdf

tests:
asdf-test:
main: Spec.hs
source-dirs: test
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- asdf
- asdf
7 changes: 7 additions & 0 deletions test/testdata/addPackageTest/hpack-lib/app/Asdf.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{-# LANGUAGE OverloadedStrings #-}

module Asdf where

import Codec.Compression.GZip

main = return $ compress "hello"
25 changes: 25 additions & 0 deletions test/testdata/addPackageTest/hpack-lib/package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: asdf
version: 0.1.0.0
github: "githubuser/asdf"
license: BSD3
author: "Author name here"
maintainer: "[email protected]"
copyright: "2018 Author name here"

extra-source-files:
- README.md
- ChangeLog.md

# Metadata used when publishing your package
# synopsis: Short description of your package
# category: Web

# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description: Please see the README on GitHub at <https://github.com/githubuser/asdf#readme>

library:
source-dirs: app
dependencies:
- base >= 4.7 && < 5
2 changes: 2 additions & 0 deletions test/testdata/addPackageTest/hybrid-exe/AddPackage.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Data.Text
foo = pack "I'm a Text"
5 changes: 5 additions & 0 deletions test/testdata/addPackageTest/hybrid-exe/app/Asdf.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}

import Codec.Compression.GZip

main = return $ compress "hello"
60 changes: 60 additions & 0 deletions test/testdata/addPackageTest/hybrid-exe/asdf.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
cabal-version: 1.12

-- This file has been generated from package.yaml by hpack version 0.31.2.
--
-- see: https://github.com/sol/hpack
--
-- hash: a63a1c272a979a805027c5855cbe062ec4698b6ea6dbe59dd5f7aa34b15656a6

name: asdf
version: 0.1.0.0
description: Please see the README on GitHub at <https://github.com/githubuser/asdf#readme>
homepage: https://github.com/githubuser/asdf#readme
bug-reports: https://github.com/githubuser/asdf/issues
author: Author name here
maintainer: [email protected]
copyright: 2018 Author name here
license: BSD3
build-type: Simple
extra-source-files:
README.md
ChangeLog.md

source-repository head
type: git
location: https://github.com/githubuser/asdf

library
other-modules:
Paths_asdf
hs-source-dirs:
src
build-depends:
base >=4.7 && <5
default-language: Haskell2010

executable asdf-exe
main-is: Main.hs
other-modules:
Asdf
Paths_asdf
hs-source-dirs:
app
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends:
asdf
, base >=4.7 && <5
default-language: Haskell2010

test-suite asdf-test
type: exitcode-stdio-1.0
main-is: Spec.hs
other-modules:
Paths_asdf
hs-source-dirs:
test
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends:
asdf
, base >=4.7 && <5
default-language: Haskell2010
Loading