Skip to content

Commit aec4a23

Browse files
authored
[HaskellPlanner] enable shell and build for Stack framework (#155)
## Summary This PR enables **basic** Haskell support using the Stack framework. Both `devbox shell` and `devbox build` should work. Limitations: - haskell (ghc) version: We currently support the latest versions of haskell. Have not implemented parsing `stack.yaml` or `package.yaml` or `<project-name>.cabal` for the haskell version and installing just that. - image size: The Runtime docker image is quite large (2 GB). I added a comment inline on a way to possibly reduce this size, but haven't fully verified it will work in all circumstances. ## How was it tested? Added a basic hello world example using `stack new hello-world` generated code. did: - `devbox shell` and then `stack exec hello-world-exe - `devbox build` -> ran with `docker run devbox`
1 parent 2d17402 commit aec4a23

File tree

13 files changed

+324
-2
lines changed

13 files changed

+324
-2
lines changed

planner/languages/haskell/haskell_planner.go

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,30 @@
44
package haskell
55

66
import (
7+
"fmt"
8+
9+
"github.com/pkg/errors"
10+
"go.jetpack.io/devbox/cuecfg"
711
"go.jetpack.io/devbox/planner/plansdk"
812
)
913

14+
const (
15+
packageYaml = "package.yaml"
16+
stackYaml = "stack.yaml"
17+
)
18+
19+
// This Project struct corresponds to the package.yaml generated during `stack new <project-name>`.
20+
// The generated code will have stack.yaml, package.yaml and <project-name>.cabal files. This can be
21+
// rather confusing. In short:
22+
// - stack.yaml: has project config
23+
// - package.yaml: has a description of the package
24+
// - <project-name>.cabal: also has a description of the package but in "cabal file format".
25+
//
26+
// Cabal is an older build system for Haskell, while Stack is more modern, so I think Stack wraps over Cabal.
27+
type Project struct {
28+
Name string `yaml:"name,omitempty"`
29+
}
30+
1031
type Planner struct{}
1132

1233
// Implements interface Planner (compile-time check)
@@ -17,9 +38,74 @@ func (p *Planner) Name() string {
1738
}
1839

1940
func (p *Planner) IsRelevant(srcDir string) bool {
20-
return false
41+
a, err := plansdk.NewAnalyzer(srcDir)
42+
if err != nil {
43+
// We should log that an error has occurred.
44+
return false
45+
}
46+
isRelevant := a.HasAnyFile(stackYaml)
47+
48+
return isRelevant
2149
}
2250

2351
func (p *Planner) GetPlan(srcDir string) *plansdk.Plan {
24-
return &plansdk.Plan{}
52+
plan, err := p.getPlan(srcDir)
53+
if err != nil {
54+
return nil
55+
}
56+
return plan
57+
}
58+
59+
func (p *Planner) getPlan(srcDir string) (*plansdk.Plan, error) {
60+
61+
project, err := getProject(srcDir)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
exeName := fmt.Sprintf("%s-exe", project.Name)
67+
packages := []string{"stack", "libiconv", "libffi", "binutils", "ghc"}
68+
69+
return &plansdk.Plan{
70+
DevPackages: packages,
71+
RuntimePackages: packages,
72+
InstallStage: &plansdk.Stage{
73+
InputFiles: []string{"."},
74+
Command: "stack build --system-ghc --dependencies-only",
75+
},
76+
BuildStage: &plansdk.Stage{
77+
Command: "stack build --system-ghc",
78+
},
79+
StartStage: &plansdk.Stage{
80+
// The image size can be very large (> 2GB). Consider copying the binary
81+
// from `$(stack path --local-install-root --system-ghc)/bin`. Not doing
82+
// it because I haven't investigated if this would work in all scenarios.
83+
// Idea from: https://gist.github.com/TimWSpence/9b89b0915bf5224128e4b96abfd4ce02
84+
// https://medium.com/permutive/optimized-docker-builds-for-haskell-76a9808eb10b
85+
InputFiles: []string{"."},
86+
Command: fmt.Sprintf("stack exec --system-ghc %s", exeName),
87+
},
88+
}, nil
89+
}
90+
91+
func getProject(srcDir string) (*Project, error) {
92+
93+
a, err := plansdk.NewAnalyzer(srcDir)
94+
if err != nil {
95+
// We should log that an error has occurred.
96+
return nil, err
97+
}
98+
paths := a.GlobFiles(packageYaml)
99+
if len(paths) < 1 {
100+
return nil, errors.Errorf(
101+
"expected to find a %s file in directory %s",
102+
packageYaml,
103+
srcDir,
104+
)
105+
}
106+
projectFilePath := paths[0]
107+
108+
project := &Project{}
109+
err = cuecfg.ParseFile(projectFilePath, &project)
110+
return project, err
25111
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.stack-work/
2+
*~
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Changelog for `hello-world`
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to the
7+
[Haskell Package Versioning Policy](https://pvp.haskell.org/).
8+
9+
## Unreleased
10+
11+
## 0.1.0.0 - YYYY-MM-DD
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# hello-world
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Distribution.Simple
2+
main = defaultMain
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Main (main) where
2+
3+
import Lib
4+
5+
main :: IO ()
6+
main = helloWorldFunc
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"packages": [],
3+
"shell": {
4+
"init_hook": null
5+
}
6+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
cabal-version: 1.12
2+
3+
-- This file has been generated from package.yaml by hpack version 0.34.7.
4+
--
5+
-- see: https://github.com/sol/hpack
6+
7+
name: hello-world
8+
version: 0.1.0.0
9+
description: Please see the README on GitHub at <https://github.com/githubuser/hello-world#readme>
10+
homepage: https://github.com/githubuser/hello-world#readme
11+
bug-reports: https://github.com/githubuser/hello-world/issues
12+
author: Author name here
13+
maintainer: [email protected]
14+
copyright: 2022 Author name here
15+
license: BSD3
16+
license-file: LICENSE
17+
build-type: Simple
18+
extra-source-files:
19+
README.md
20+
CHANGELOG.md
21+
22+
source-repository head
23+
type: git
24+
location: https://github.com/githubuser/hello-world
25+
26+
library
27+
exposed-modules:
28+
Lib
29+
other-modules:
30+
Paths_hello_world
31+
hs-source-dirs:
32+
src
33+
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
34+
build-depends:
35+
base >=4.7 && <5
36+
default-language: Haskell2010
37+
38+
executable hello-world-exe
39+
main-is: Main.hs
40+
other-modules:
41+
Paths_hello_world
42+
hs-source-dirs:
43+
app
44+
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
45+
build-depends:
46+
base >=4.7 && <5
47+
, hello-world
48+
default-language: Haskell2010
49+
50+
test-suite hello-world-test
51+
type: exitcode-stdio-1.0
52+
main-is: Spec.hs
53+
other-modules:
54+
Paths_hello_world
55+
hs-source-dirs:
56+
test
57+
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
58+
build-depends:
59+
base >=4.7 && <5
60+
, hello-world
61+
default-language: Haskell2010
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: hello-world
2+
version: 0.1.0.0
3+
github: "githubuser/hello-world"
4+
license: BSD3
5+
author: "Author name here"
6+
maintainer: "[email protected]"
7+
copyright: "2022 Author name here"
8+
9+
extra-source-files:
10+
- README.md
11+
- CHANGELOG.md
12+
13+
# Metadata used when publishing your package
14+
# synopsis: Short description of your package
15+
# category: Web
16+
17+
# To avoid duplicated efforts in documentation and dealing with the
18+
# complications of embedding Haddock markup inside cabal files, it is
19+
# common to point users to the README.md file.
20+
description: Please see the README on GitHub at <https://github.com/githubuser/hello-world#readme>
21+
22+
dependencies:
23+
- base >= 4.7 && < 5
24+
25+
ghc-options:
26+
- -Wall
27+
- -Wcompat
28+
- -Widentities
29+
- -Wincomplete-record-updates
30+
- -Wincomplete-uni-patterns
31+
- -Wmissing-export-lists
32+
- -Wmissing-home-modules
33+
- -Wpartial-fields
34+
- -Wredundant-constraints
35+
36+
library:
37+
source-dirs: src
38+
39+
executables:
40+
hello-world-exe:
41+
main: Main.hs
42+
source-dirs: app
43+
ghc-options:
44+
- -threaded
45+
- -rtsopts
46+
- -with-rtsopts=-N
47+
dependencies:
48+
- hello-world
49+
50+
tests:
51+
hello-world-test:
52+
main: Spec.hs
53+
source-dirs: test
54+
ghc-options:
55+
- -threaded
56+
- -rtsopts
57+
- -with-rtsopts=-N
58+
dependencies:
59+
- hello-world
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Lib
2+
( helloWorldFunc
3+
) where
4+
5+
helloWorldFunc :: IO ()
6+
helloWorldFunc = putStrLn "Hello World!"

0 commit comments

Comments
 (0)