Skip to content

Commit

Permalink
start of php provider
Browse files Browse the repository at this point in the history
  • Loading branch information
coffee-cup committed Jan 23, 2025
1 parent ccf176e commit 2d775b0
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 48 deletions.
3 changes: 2 additions & 1 deletion buildkit/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func ConvertPlanToLLB(plan *p.BuildPlan, opts ConvertPlanOptions) (*llb.State, *

func getStartState(buildState llb.State, plan *p.BuildPlan, platform specs.Platform) llb.State {
startState := buildState
startState.Dir(WorkingDir)

if plan.Start.BaseImage != "" {
// This is all the user code + any modifications made by the providers
Expand All @@ -96,7 +97,7 @@ func getStartState(buildState llb.State, plan *p.BuildPlan, platform specs.Platf
// If there is no custom start image, we will just copy over any additional files from the local context
src := llb.Local("context")
for _, path := range plan.Start.Paths {
startState = startState.File(llb.Copy(src, path, path, &llb.CopyInfo{
startState = startState.Dir(WorkingDir).File(llb.Copy(src, path, path, &llb.CopyInfo{
CreateDestPath: true,
FollowSymlinks: true,
CopyDirContentsOnly: true,
Expand Down
9 changes: 7 additions & 2 deletions buildkit/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,14 @@ func (g *BuildGraph) convertCommandToLLB(node *Node, cmd plan.Command, state llb

if cmd.CacheKey != "" {
if planCache, ok := g.Plan.Caches[cmd.CacheKey]; ok {
cache := g.CacheStore.GetCache(cmd.CacheKey, &planCache)
cache := g.CacheStore.GetCache(cmd.CacheKey, planCache)
cacheType := llb.CacheMountShared
if planCache.Type == plan.CacheTypeLocked {
cacheType = llb.CacheMountLocked
}

opts = append(opts,
llb.AddMount(planCache.Directory, *cache.cacheState, llb.AsPersistentCacheDir(cache.cacheKey, llb.CacheMountShared)),
llb.AddMount(planCache.Directory, *cache.cacheState, llb.AsPersistentCacheDir(cache.cacheKey, cacheType)),
)
} else {
return state, fmt.Errorf("cache with key %q not found", cmd.CacheKey)
Expand Down
1 change: 1 addition & 0 deletions core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func GenerateBuildPlan(app *app.App, env *app.Environment, options *GenerateBuil

buildStepOptions := &generate.BuildStepOptions{
ResolvedPackages: resolvedPackages,
Caches: ctx.Caches,
}

buildPlan.Variables = ctx.Variables
Expand Down
7 changes: 4 additions & 3 deletions core/generate/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

type BuildStepOptions struct {
ResolvedPackages map[string]*resolver.ResolvedPackage
Caches map[string]*plan.Cache
}

type StepBuilder interface {
Expand All @@ -21,7 +22,7 @@ type GenerateContext struct {
Variables map[string]string
Steps []StepBuilder
Start StartContext
Caches map[string]plan.Cache
Caches map[string]*plan.Cache

resolver *resolver.Resolver
}
Expand All @@ -38,7 +39,7 @@ func NewGenerateContext(app *a.App, env *a.Environment) (*GenerateContext, error
Variables: map[string]string{},
Steps: make([]StepBuilder, 0),
Start: *NewStartContext(),
Caches: make(map[string]plan.Cache),
Caches: make(map[string]*plan.Cache),
resolver: resolver,
}, nil
}
Expand All @@ -48,6 +49,6 @@ func (c *GenerateContext) ResolvePackages() (map[string]*resolver.ResolvedPackag
}

func (c *GenerateContext) AddCache(key string, directory string) string {
c.Caches[key] = *plan.NewCache(directory)
c.Caches[key] = plan.NewCache(directory)
return key
}
110 changes: 69 additions & 41 deletions core/generate/miseContext.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type PackageStepBuilder struct {
SupportingMiseFiles []string
Assets map[string]string
DependsOn []string
Outputs []string

app *a.App
env *a.Environment
Expand All @@ -35,9 +36,9 @@ func (c *GenerateContext) NewPackageStep(name string) *PackageStepBuilder {
SupportingAptPackages: []string{},
Assets: map[string]string{},
DependsOn: []string{},

app: c.App,
env: c.Env,
Outputs: []string{"/mise/shims", "/mise/installs", "/usr/local/bin/mise", "/etc/mise/config.toml", "/root/.local/state/mise"},
app: c.App,
env: c.Env,
}

c.Steps = append(c.Steps, step)
Expand Down Expand Up @@ -70,64 +71,91 @@ func (b *PackageStepBuilder) Build(options *BuildStepOptions) (*plan.Step, error

step.DependsOn = b.DependsOn

aptCache, ok := options.Caches["apt"]

Check failure on line 74 in core/generate/miseContext.go

View workflow job for this annotation

GitHub Actions / Check and Test

SA4006: this value of `aptCache` is never used (staticcheck)
if !ok {
aptCache = plan.NewCache("/var/cache/apt")
aptCache.Type = plan.CacheTypeLocked
options.Caches["apt"] = aptCache
}

miseCache, ok := options.Caches["mise"]

Check failure on line 81 in core/generate/miseContext.go

View workflow job for this annotation

GitHub Actions / Check and Test

ineffectual assignment to miseCache (ineffassign)
if !ok {
miseCache = plan.NewCache("/mise/cache")
options.Caches["mise"] = miseCache
}

// Install mise
step.AddCommands([]plan.Command{
plan.NewVariableCommand("MISE_DATA_DIR", "/mise"),
plan.NewVariableCommand("MISE_CONFIG_DIR", "/mise"),
plan.NewVariableCommand("MISE_INSTALL_PATH", "/usr/local/bin/mise"),
plan.NewPathCommand("/mise/shims"),
plan.NewExecCommand("sh -c 'apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && rm -rf /var/lib/apt/lists/*'", plan.ExecOptions{CustomName: "install curl"}),
plan.NewExecCommand("sh -c 'curl -fsSL https://mise.run | sh'",
plan.ExecOptions{CustomName: "install mise"}),
})

// Add user mise config files if they exist
supportingMiseConfigFiles := b.GetSupportingMiseConfigFiles(b.app.Source)
for _, file := range supportingMiseConfigFiles {
if len(b.MisePackages) > 0 {
step.AddCommands([]plan.Command{
plan.NewCopyCommand(file, "/app/"+file),
plan.NewVariableCommand("MISE_DATA_DIR", "/mise"),
plan.NewVariableCommand("MISE_CONFIG_DIR", "/mise"),
plan.NewVariableCommand("MISE_INSTALL_PATH", "/usr/local/bin/mise"),
plan.NewVariableCommand("MISE_CACHE_DIR", "/mise/cache"),
plan.NewPathCommand("/mise/shims"),
plan.NewExecCommand("sh -c 'apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && rm -rf /var/lib/apt/lists/*'", plan.ExecOptions{
CustomName: "install curl",
CacheKey: "apt",
}),
plan.NewExecCommand("sh -c 'curl -fsSL https://mise.run | sh'",
plan.ExecOptions{
CustomName: "install mise",
CacheKey: "mise",
}),
})

// Add user mise config files if they exist
supportingMiseConfigFiles := b.GetSupportingMiseConfigFiles(b.app.Source)
for _, file := range supportingMiseConfigFiles {
step.AddCommands([]plan.Command{
plan.NewCopyCommand(file, "/app/"+file),
})
}
}

// Setup apt commands
if len(b.SupportingAptPackages) > 0 {
pkgString := strings.Join(b.SupportingAptPackages, " ")
step.AddCommands([]plan.Command{
plan.NewExecCommand("sh -c 'apt-get update && apt-get install -y "+pkgString+" && rm -rf /var/lib/apt/lists/*'",
plan.ExecOptions{CustomName: "install apt packages: " + pkgString}),
plan.NewExecCommand("sh -c 'apt-get update && apt-get install -y "+pkgString+" && rm -rf /var/lib/apt/lists/*'", plan.ExecOptions{
CustomName: "install apt packages: " + pkgString,
CacheKey: "apt",
}),
})
}

// Setup mise commands
packagesToInstall := make(map[string]string)
for _, pkg := range b.MisePackages {
resolved, ok := options.ResolvedPackages[pkg.Name]
if ok && resolved.ResolvedVersion != nil {
packagesToInstall[pkg.Name] = *resolved.ResolvedVersion
if len(b.MisePackages) > 0 {
packagesToInstall := make(map[string]string)
for _, pkg := range b.MisePackages {
resolved, ok := options.ResolvedPackages[pkg.Name]
if ok && resolved.ResolvedVersion != nil {
packagesToInstall[pkg.Name] = *resolved.ResolvedVersion
}
}
}

miseToml, err := mise.GenerateMiseToml(packagesToInstall)
if err != nil {
return nil, fmt.Errorf("failed to generate mise.toml: %w", err)
}
miseToml, err := mise.GenerateMiseToml(packagesToInstall)
if err != nil {
return nil, fmt.Errorf("failed to generate mise.toml: %w", err)
}

b.Assets["mise.toml"] = miseToml
b.Assets["mise.toml"] = miseToml

pkgNames := make([]string, 0, len(packagesToInstall))
for k := range packagesToInstall {
pkgNames = append(pkgNames, k)
}
pkgNames := make([]string, 0, len(packagesToInstall))
for k := range packagesToInstall {
pkgNames = append(pkgNames, k)
}

step.AddCommands([]plan.Command{
plan.NewFileCommand("/etc/mise/config.toml", "mise.toml", "create mise config"),
plan.NewExecCommand("sh -c 'mise trust -a && mise install'",
plan.ExecOptions{CustomName: "install mise packages: " + strings.Join(pkgNames, ", ")}),
})
step.AddCommands([]plan.Command{
plan.NewFileCommand("/etc/mise/config.toml", "mise.toml", "create mise config"),
plan.NewExecCommand("sh -c 'mise trust -a && mise install'", plan.ExecOptions{
CustomName: "install mise packages: " + strings.Join(pkgNames, ", "),
CacheKey: "mise",
}),
})
}

step.Assets = b.Assets

step.Outputs = []string{"/mise/shims", "/mise/installs", "/usr/local/bin/mise", "/etc/mise/config.toml", "/root/.local/state/mise"}
step.Outputs = b.Outputs

return step, nil
}
Expand Down
7 changes: 7 additions & 0 deletions core/plan/cache.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package plan

const (
CacheTypeShared = "shared"
CacheTypeLocked = "locked"
)

type Cache struct {
Directory string `json:"directory,omitempty"`
Type string `json:"type,omitempty"`
}

func NewCache(directory string) *Cache {
return &Cache{
Directory: directory,
Type: CacheTypeShared,
}
}
2 changes: 1 addition & 1 deletion core/plan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ type BuildPlan struct {
Variables map[string]string `json:"variables,omitempty"`
Steps []Step `json:"steps,omitempty"`
Start Start `json:"start,omitempty"`
Caches map[string]Cache `json:"caches,omitempty"`
Caches map[string]*Cache `json:"caches,omitempty"`
}

type Start struct {
Expand Down
77 changes: 77 additions & 0 deletions core/providers/php/php.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package php

import (
"github.com/railwayapp/railpack-go/core/generate"
)

const (
DEFAULT_PHP_VERSION = "8.4"
)

type PhpProvider struct{}

func (p *PhpProvider) Name() string {
return "php"
}

func (p *PhpProvider) Plan(ctx *generate.GenerateContext) (bool, error) {
hasPhp := ctx.App.HasMatch("index.php") ||
ctx.App.HasMatch("composer.json")

if !hasPhp {
return false, nil
}

if err := p.packages(ctx); err != nil {
return false, err
}

ctx.Start.Paths = append(ctx.Start.Paths, ".")

return false, nil
}

var runtimeAptPackages = []string{
"libgd-dev",
"libedit-dev",
"libicu-dev",
"libjpeg-dev",
"libmysqlclient-dev",
"libonig-dev",
"libpng-dev",
"libpq-dev",
"libreadline-dev",
"libsqlite3-dev",
"libssl-dev",
"libxml2-dev",
"libzip-dev",
"openssl",
"libcurl4-openssl-dev",
}

// These packages (+ runtime) are only needed for building php
// They are not included at runtime
var buildAptPackages = []string{
"gettext",
"curl",
"git",
"autoconf",
"build-essential",
"bison",
"pkg-config",
"zlib1g-dev",
"re2c",
}

func (p *PhpProvider) packages(ctx *generate.GenerateContext) error {
packages := ctx.NewPackageStep("packages")
packages.Default("php", DEFAULT_PHP_VERSION)
packages.SupportingAptPackages = runtimeAptPackages
packages.SupportingAptPackages = append(packages.SupportingAptPackages, buildAptPackages...)

runtimePackages := ctx.NewPackageStep("packages:runtime")
runtimePackages.SupportingAptPackages = runtimeAptPackages
runtimePackages.Outputs = []string{"/"}

return nil
}
2 changes: 2 additions & 0 deletions core/providers/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package providers
import (
"github.com/railwayapp/railpack-go/core/generate"
"github.com/railwayapp/railpack-go/core/providers/node"
"github.com/railwayapp/railpack-go/core/providers/php"
"github.com/railwayapp/railpack-go/core/providers/python"
)

Expand All @@ -15,5 +16,6 @@ func GetLanguageProviders() []Provider {
return []Provider{
&node.NodeProvider{},
&python.PythonProvider{},
&php.PhpProvider{},
}
}
Binary file added examples/php-vanilla/.DS_Store
Binary file not shown.
41 changes: 41 additions & 0 deletions examples/php-vanilla/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
<style>
@import url('https://fonts.bunny.net/css2?family=Nunito&display=swap');
body {
background-color: rgb(245, 234, 214);
font-family: Nunito;
font-size: large;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
text-align: center;
padding: 0;
margin: 0;
}

a {
color: #dd5500;
text-decoration: none;
transition-property: all;
transition-duration: .2s;
padding-bottom: 0px;
border-bottom: 0px solid transparent;
}
a:hover {
border-bottom-width: 2px;
border-bottom-color: #dd5500;
}
</style>
</head>
<body>
<div>
<h1>Hello World!</h1>
<p>Welcome to <a href="https://github.com/railwayapp/railpack">Railpack</a>!</p>
<p><b>PHP Version:</b> <?php echo phpversion() ?></p>
</div>
</body>
</html>

0 comments on commit 2d775b0

Please sign in to comment.