Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion internal/impl/devbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"go.jetpack.io/devbox/internal/fileutil"
"go.jetpack.io/devbox/internal/initrec"
"go.jetpack.io/devbox/internal/nix"
"go.jetpack.io/devbox/internal/pkgslice"
"go.jetpack.io/devbox/internal/planner"
"go.jetpack.io/devbox/internal/planner/plansdk"
"go.jetpack.io/devbox/internal/plugin"
Expand Down Expand Up @@ -121,7 +122,8 @@ func (d *Devbox) Config() *Config {
func (d *Devbox) ShellPlan() (*plansdk.ShellPlan, error) {
userDefinedPkgs := d.packages()
shellPlan := planner.GetShellPlan(d.projectDir, userDefinedPkgs)
shellPlan.DevPackages = userDefinedPkgs

shellPlan.DevPackages = pkgslice.Unique(append(shellPlan.DevPackages, userDefinedPkgs...))

nixpkgsInfo, err := plansdk.GetNixpkgsInfo(d.cfg.Nixpkgs.Commit)
if err != nil {
Expand Down
132 changes: 132 additions & 0 deletions internal/planner/languages/haskell/haskell_v1_planner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2022 Jetpack Technologies Inc and contributors. All rights reserved.
// Use of this source code is governed by the license in the LICENSE file.

package haskell

import (
"fmt"
"regexp"
"strings"

"go.jetpack.io/devbox/internal/planner/plansdk"
)

var haskellPackageRegex = regexp.MustCompile(`^haskellPackages.[a-zA-Z0-9\-\_\.]*$`)

// Use matchgroups to get the compiler, then the package name
// Compiler will be in Mathches[0][1] and package name in Matches[1][2]
var haskellPackageVersionRegex = regexp.MustCompile(`^haskell\.packages\.(.*)\.(.*)$`)

var ghcRegex = regexp.MustCompile(`^ghc$`)
var ghcVersionRegex = regexp.MustCompile(`^haskell\.compiler\.(.*)$`)

var stackRegex = regexp.MustCompile(`^stack$`)
var cabalRegex = regexp.MustCompile(`^cabal-install$`)

type V2Planner struct {
userPackages []string
}

// Create types and struct that tells us which GHC compiler we're using

type CompilerType string

const (
Default CompilerType = "default"
Versioned CompilerType = "versioned"
None CompilerType = ""
)

type GHCPackage struct {
compilerType CompilerType
pkg string
}

var _ plansdk.PlannerForPackages = (*V2Planner)(nil)

func (p *V2Planner) Name() string {
return "haskell.v1.Planner"
}

func (p *V2Planner) IsRelevant(srcDir string) bool {
return false
}

func (p *V2Planner) IsRelevantForPackages(packages []string) bool {
p.userPackages = packages
Comment on lines +55 to +56
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of scope for this PR, but this is a strange API, where we save packages as a side-effect in a boolean IsRelevantForPackages function call....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also weird that we do 2 passes of the regex, one to detect if the planner is relevant, and another to actually create the shell plan -- should we be storing the results of the IsRelevantForPackages and then reusing it in GetShellPlan?

_, index := p.getGHCPackage()
return index != -1
}

func (p *V2Planner) GetShellPlan(srcDir string) *plansdk.ShellPlan {
ghcPackage, index := p.getGHCPackage()
// Remove the ghc package from the list of user packages
p.userPackages = append(p.userPackages[:index], p.userPackages[index+1:]...)
haskellPackages := p.getHaskellPackages(ghcPackage)
definitions := []string{}
// Create the haskell-pkg definition based on the compiler type
switch ghcPackage.compilerType {
case Default:
definitions = []string{
fmt.Sprintf(
"haskell-pkg = pkgs.haskellPackages.ghcWithPackages (ps: with ps; [ %s ]);",
strings.Join(haskellPackages, " "),
),
}
case Versioned:
definitions = []string{
fmt.Sprintf(
"haskell-pkg = pkgs.haskell.packages.%s.ghcWithPackages (ps: with ps; [ %s ]);",
ghcPackage.pkg,
strings.Join(haskellPackages, " "),
),
}
}
return &plansdk.ShellPlan{
Definitions: definitions,
// Prepend "haskell-pkg" to the list of user packages
DevPackages: append([]string{"haskell-pkg"}, p.userPackages...),
}
}

func (p *V2Planner) getGHCPackage() (GHCPackage, int) {
// packages := p.userPackages
for index, pkg := range p.userPackages {
if ghcRegex.Match([]byte(pkg)) {
return GHCPackage{compilerType: Default, pkg: pkg}, index
} else if matches := ghcVersionRegex.FindStringSubmatch(pkg); matches != nil {
return GHCPackage{compilerType: Versioned, pkg: matches[1]}, index
}
}
return GHCPackage{compilerType: None, pkg: ""}, -1
}

func (p *V2Planner) getHaskellPackages(ghcPackage GHCPackage) []string {
var haskellPackages []string
var filteredPackages []string
for _, pkg := range p.userPackages {
switch ghcPackage.compilerType {
case Default:
if stackRegex.Match([]byte(pkg)) || cabalRegex.Match([]byte(pkg)) {
haskellPackages = append(haskellPackages, pkg)
} else if haskellPackageRegex.Match([]byte(pkg)) {
haskellPackages = append(haskellPackages, strings.Split(pkg, ".")[1])
} else {
filteredPackages = append(filteredPackages, pkg)
}
case Versioned:
if matches := haskellPackageVersionRegex.FindAllStringSubmatch(pkg, -1); matches != nil {
fmt.Println(matches)
if matches[0][1] == ghcPackage.pkg {
haskellPackages = append(haskellPackages, matches[0][2])
}
} else {
filteredPackages = append(filteredPackages, pkg)
}
}
}
// Remove the packages that we've already added to the haskellPackages
// list from the userPackages list
p.userPackages = filteredPackages
return haskellPackages
}
11 changes: 3 additions & 8 deletions internal/planner/planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import (
"context"
"runtime/trace"

"github.com/samber/lo"
"go.jetpack.io/devbox/internal/planner/languages/haskell"
"go.jetpack.io/devbox/internal/planner/languages/php"
"go.jetpack.io/devbox/internal/planner/plansdk"
)

var PLANNERS = []plansdk.Planner{
&php.V2Planner{},
&haskell.V2Planner{},
}

// Return a merged shell plan from shell planners if user defined packages
Expand All @@ -24,13 +25,7 @@ func GetShellPlan(srcDir string, userPkgs []string) *plansdk.ShellPlan {
result := &plansdk.ShellPlan{}
planners := getRelevantPlanners(srcDir, userPkgs)
for _, p := range planners {
pkgs := p.GetShellPlan(srcDir).DevPackages
mutualPkgs := lo.Intersect(userPkgs, pkgs)
// Only apply shell plan if user packages list all the packages from shell plan.
if len(mutualPkgs) == len(pkgs) {
// if merge fails, we return no errors for now.
result, _ = plansdk.MergeShellPlans(result, p.GetShellPlan(srcDir))
Comment on lines -27 to -32
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is safe given our haskell and php planners. The getRelevantPlanners should filter appropriately.

}
result, _ = plansdk.MergeShellPlans(result, p.GetShellPlan(srcDir))
}
return result
}
Expand Down