Skip to content

Commit

Permalink
[PHP] Add extension support and better version detection (#113)
Browse files Browse the repository at this point in the history
## Summary

Use composer.json to figure out if any extensions should be installed.
Also, improved version detection (using `require` instead of `config`)

@loreto should we be using `systemPackages` instead of packages for
languages?

## How was it tested?

```
devbox shell
php -m | grep mbstring 
```
  • Loading branch information
mikeland73 authored Sep 13, 2022
1 parent 1be7c03 commit 4568b9d
Show file tree
Hide file tree
Showing 13 changed files with 125 additions and 36 deletions.
6 changes: 5 additions & 1 deletion devbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ func (d *Devbox) Plan() (*plansdk.Plan, error) {
SharedPlan: d.cfg.SharedPlan,
}

return plansdk.MergeUserPlan(userPlan, planner.GetPlan(d.srcDir))
automatedPlan, err := planner.GetPlan(d.srcDir)
if err != nil {
return nil, err
}
return plansdk.MergeUserPlan(userPlan, automatedPlan)
}

// Generate creates the directory of Nix files and the Dockerfile that define
Expand Down
2 changes: 2 additions & 0 deletions devbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ func assertPlansMatch(t *testing.T, expected *plansdk.Plan, actual *plansdk.Plan
assert.ElementsMatch(expected.InstallStage.GetInputFiles(), getFileNames(actual.InstallStage.GetInputFiles()), "InstallStage.InputFiles should match")
assert.ElementsMatch(expected.BuildStage.GetInputFiles(), getFileNames(actual.BuildStage.GetInputFiles()), "BuildStage.InputFiles should match")
assert.ElementsMatch(expected.StartStage.GetInputFiles(), getFileNames(actual.StartStage.GetInputFiles()), "StartStage.InputFiles should match")

assert.ElementsMatch(expected.Definitions, actual.Definitions, "Definitions should match")
}

func fileExists(path string) bool {
Expand Down
9 changes: 3 additions & 6 deletions examples/php/composer.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
{
"require": {
"slim/slim": "^4.10",
"slim/psr7": "^1.5"
},
"config": {
"platform": {
"php": "8.1.10"
}
"slim/psr7": "^1.5",
"ext-mbstring": "*",
"php": "^8.1"
}
}
5 changes: 4 additions & 1 deletion examples/php/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 65 additions & 12 deletions planner/languages/php/php_planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"path/filepath"
"strings"

"github.com/pkg/errors"
"go.jetpack.io/devbox/boxcli/usererr"
"go.jetpack.io/devbox/planner/plansdk"
)
Expand Down Expand Up @@ -50,6 +51,7 @@ func (p *Planner) GetPlan(srcDir string) *plansdk.Plan {
fmt.Sprintf("php%s", v.MajorMinorConcatenated()),
fmt.Sprintf("php%sPackages.composer", v.MajorMinorConcatenated()),
},
Definitions: p.definitions(srcDir, v),
}
if !plansdk.FileExists(filepath.Join(srcDir, "public/index.php")) {
return plan.WithError(usererr.New("Can't build. No public/index.php found."))
Expand All @@ -66,28 +68,33 @@ func (p *Planner) GetPlan(srcDir string) *plansdk.Plan {
return plan
}

type composerPackages struct {
Config struct {
Platform struct {
PHP string `json:"php"`
} `json:"platform"`
} `json:"config"`
Require map[string]string `json:"require"`
}

func (p *Planner) version(srcDir string) *plansdk.Version {
latestVersion, _ := plansdk.NewVersion(supportedPHPVersions[0])
composerJSONPath := filepath.Join(srcDir, "composer.json")
content, err := os.ReadFile(composerJSONPath)
project, err := p.parseComposerPackages(srcDir)

if err != nil {
return latestVersion
}

composerJSON := struct {
Config struct {
Platform struct {
PHP string `json:"php"`
} `json:"platform"`
} `json:"config"`
}{}
if err := json.Unmarshal(content, &composerJSON); err != nil ||
composerJSON.Config.Platform.PHP == "" {
composerPHPVersion := project.Require["php"]
if composerPHPVersion == "" {
composerPHPVersion = project.Config.Platform.PHP
}

if composerPHPVersion == "" {
return latestVersion
}

version, err := plansdk.NewVersion(composerJSON.Config.Platform.PHP)
version, err := plansdk.NewVersion(composerPHPVersion)
if err != nil {
return latestVersion
}
Expand All @@ -110,3 +117,49 @@ func (p *Planner) version(srcDir string) *plansdk.Version {
// might as well pick the latest version.
return latestVersion
}

func (p *Planner) definitions(srcDir string, v *plansdk.Version) []string {
extensions, err := p.extensions(srcDir)
if len(extensions) == 0 || err != nil {
return []string{}
}
return []string{
fmt.Sprintf(
"php%s = pkgs.php%s.withExtensions ({ enabled, all }: enabled ++ (with all; [ %s ]));",
v.MajorMinorConcatenated(),
v.MajorMinorConcatenated(),
strings.Join(extensions, " "),
),
}
}

func (p *Planner) extensions(srcDir string) ([]string, error) {
project, err := p.parseComposerPackages(srcDir)
if err != nil {
return nil, errors.WithStack(err)
}

extensions := []string{}
for requirement := range project.Require {
if strings.HasPrefix(requirement, "ext-") {
name := strings.Split(requirement, "-")[1]
if name != "" && name != "json" {
extensions = append(extensions, name)
}
}
}

return extensions, nil
}

func (p *Planner) parseComposerPackages(srcDir string) (*composerPackages, error) {
composerJSONPath := filepath.Join(srcDir, "composer.json")
content, err := os.ReadFile(composerJSONPath)

if err != nil {
return nil, errors.WithStack(err)
}

project := &composerPackages{}
return project, errors.WithStack(json.Unmarshal(content, project))
}
11 changes: 8 additions & 3 deletions planner/planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,20 @@ var PLANNERS = []plansdk.Planner{
&zig.Planner{},
}

func GetPlan(srcDir string) *plansdk.Plan {
func GetPlan(srcDir string) (*plansdk.Plan, error) {
result := &plansdk.Plan{
DevPackages: []string{},
RuntimePackages: []string{},
}
var err error
for _, p := range getRelevantPlanners(srcDir) {
result = plansdk.MergePlans(result, p.GetPlan(srcDir))
result, err = plansdk.MergePlans(result, p.GetPlan(srcDir))
if err != nil {
return nil, err
}

}
return result
return result, nil
}

func IsBuildable(srcDir string) (bool, error) {
Expand Down
21 changes: 12 additions & 9 deletions planner/plansdk/plansdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ type Plan struct {
// application.
RuntimePackages []string `cue:"[...string]" json:"runtime_packages"`

Definitions []string `cue:"[...string]" json:"definitions"`

Errors []PlanError `json:"errors,omitempty"`
}

Expand Down Expand Up @@ -118,31 +120,29 @@ func (p *Plan) WithError(err error) *Plan {
return p
}

func MergePlans(plans ...*Plan) *Plan {
plan := &Plan{
DevPackages: []string{},
RuntimePackages: []string{},
}
func MergePlans(plans ...*Plan) (*Plan, error) {
plan := &Plan{}
for _, p := range plans {
err := mergo.Merge(
plan,
&Plan{
DevPackages: p.DevPackages,
RuntimePackages: p.RuntimePackages,
Definitions: p.Definitions,
},
// Only WithAppendSlice the dev and runtime packages field.
// Only WithAppendSlice definitions, dev, and runtime packages field.
mergo.WithAppendSlice,
)
if err != nil {
panic(err) // TODO: propagate error.
return nil, err
}
}

plan.DevPackages = pkgslice.Unique(plan.DevPackages)
plan.RuntimePackages = pkgslice.Unique(plan.RuntimePackages)
plan.SharedPlan = findBuildablePlan(plans...).SharedPlan

return plan
return plan, nil
}

func findBuildablePlan(plans ...*Plan) *Plan {
Expand All @@ -156,7 +156,10 @@ func findBuildablePlan(plans ...*Plan) *Plan {
}

func MergeUserPlan(userPlan *Plan, automatedPlan *Plan) (*Plan, error) {
plan := MergePlans(userPlan, automatedPlan)
plan, err := MergePlans(userPlan, automatedPlan)
if err != nil {
return nil, err
}
sharedPlan := &Plan{
SharedPlan: userPlan.SharedPlan,
}
Expand Down
9 changes: 6 additions & 3 deletions planner/plansdk/plansdk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ func TestMergePlans(t *testing.T) {
RuntimePackages: []string{"a", "b", "c"},
SharedPlan: SharedPlan{},
}
actual := MergePlans(plan1, plan2)
actual, err := MergePlans(plan1, plan2)
assert.NoError(t, err)
assert.Equal(t, expected, actual)

// Base plan (the first one) takes precedence:
Expand All @@ -51,7 +52,8 @@ func TestMergePlans(t *testing.T) {
},
},
}
actual = MergePlans(plan1, plan2)
actual, err = MergePlans(plan1, plan2)
assert.NoError(t, err)
assert.Equal(t, expected, actual)

// InputFiles can be overwritten:
Expand Down Expand Up @@ -80,7 +82,8 @@ func TestMergePlans(t *testing.T) {
},
},
}
actual = MergePlans(plan1, plan2)
actual, err = MergePlans(plan1, plan2)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
}

Expand Down
7 changes: 7 additions & 0 deletions testdata/php/php8.1/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"require": {
"ext-mbstring": "*",
"ext-imagick": "*",
"php": "^8.1"
}
}
5 changes: 4 additions & 1 deletion testdata/php/php8.1/plan.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@
"runtime_packages": [
"php81",
"php81Packages.composer"
],
"definitions": [
"php81 = pkgs.php81.withExtensions ({ enabled, all }: enabled ++ (with all; [ mbstring imagick ]));"
]
}
}
3 changes: 3 additions & 0 deletions tmpl/development.nix.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ let
url = "https://github.com/nixos/nixpkgs/archive/af9e00071d0971eb292fd5abef334e66eda3cb69.tar.gz";
sha256 = "1mdwy0419m5i9ss6s5frbhgzgyccbwycxm5nal40c8486bai0hwy";
}) {};
{{- range .Definitions}}
{{.}}
{{end -}}
in with pkgs;
buildEnv {
name = "devbox-development";
Expand Down
3 changes: 3 additions & 0 deletions tmpl/runtime.nix.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ let
url = "https://github.com/nixos/nixpkgs/archive/af9e00071d0971eb292fd5abef334e66eda3cb69.tar.gz";
sha256 = "1mdwy0419m5i9ss6s5frbhgzgyccbwycxm5nal40c8486bai0hwy";
}) {};
{{- range .Definitions}}
{{.}}
{{end -}}
in with pkgs;
buildEnv {
name = "devbox-runtime";
Expand Down
3 changes: 3 additions & 0 deletions tmpl/shell.nix.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ let
url = "https://github.com/nixos/nixpkgs/archive/af9e00071d0971eb292fd5abef334e66eda3cb69.tar.gz";
sha256 = "1mdwy0419m5i9ss6s5frbhgzgyccbwycxm5nal40c8486bai0hwy";
}) {};
{{- range .Definitions}}
{{.}}
{{end -}}
in with pkgs;
mkShell {
shellHook =
Expand Down

0 comments on commit 4568b9d

Please sign in to comment.