Skip to content

Commit db37b31

Browse files
Add support for pie
1 parent 6925d21 commit db37b31

File tree

8 files changed

+224
-37
lines changed

8 files changed

+224
-37
lines changed

commands/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func CommonCommands() []*console.Command {
5252
localCommands := []*console.Command{
5353
binConsoleWrapper,
5454
composerWrapper,
55+
pieWrapper,
5556
phpWrapper,
5657
bookCheckReqsCmd,
5758
bookCheckoutCmd,
@@ -143,6 +144,7 @@ func WelcomeAction(c *console.Context) error {
143144
localServerStopCmd,
144145
localSecurityCheckCmd,
145146
composerWrapper,
147+
pieWrapper,
146148
binConsoleWrapper,
147149
phpWrapper,
148150
})

commands/wrappers.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ var (
3737
return console.IncorrectUsageError{ParentError: errors.New(`This command can only be run as "symfony composer"`)}
3838
},
3939
}
40+
pieWrapper = &console.Command{
41+
Usage: "Runs PIE",
42+
Hidden: console.Hide,
43+
// we use an alias to avoid the command being shown in the help but
44+
// still be available for completion
45+
Aliases: []*console.Alias{{Name: "pie"}},
46+
Action: func(c *console.Context) error {
47+
return console.IncorrectUsageError{ParentError: errors.New(`This command can only be run as "symfony pie"`)}
48+
},
49+
}
4050
binConsoleWrapper = &console.Command{
4151
Usage: "Runs the Symfony Console (bin/console) for current project",
4252
Hidden: console.Hide,

local/php/assert.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package php
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"os"
7+
)
8+
9+
// assertIsPHPScript checks that the composer file is indeed a phar/PHP script (not a .bat file)
10+
func assertIsPHPScript(path string) bool {
11+
if path == "" {
12+
return false
13+
}
14+
file, err := os.Open(path)
15+
if err != nil {
16+
return false
17+
}
18+
defer file.Close()
19+
reader := bufio.NewReader(file)
20+
byteSlice, _, err := reader.ReadLine()
21+
if err != nil {
22+
return false
23+
}
24+
25+
if bytes.Equal(byteSlice, []byte("<?php")) {
26+
return true
27+
}
28+
29+
return bytes.HasPrefix(byteSlice, []byte("#!/")) && bytes.HasSuffix(byteSlice, []byte("php"))
30+
}

local/php/composer_test.go renamed to local/php/assert_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,24 @@ import (
2525
. "gopkg.in/check.v1"
2626
)
2727

28-
type ComposerSuite struct{}
28+
type AssertSuite struct{}
2929

30-
var _ = Suite(&ComposerSuite{})
30+
var _ = Suite(&AssertSuite{})
3131

32-
func (s *ComposerSuite) TestIsComposerPHPScript(c *C) {
32+
func (s *AssertSuite) TestAssertIsPHPScript(c *C) {
3333
dir, err := filepath.Abs("testdata/php_scripts")
3434
c.Assert(err, IsNil)
3535

36-
c.Assert(isPHPScript(""), Equals, false)
37-
c.Assert(isPHPScript(filepath.Join(dir, "unknown")), Equals, false)
38-
c.Assert(isPHPScript(filepath.Join(dir, "invalid")), Equals, false)
36+
c.Assert(assertIsPHPScript(""), Equals, false)
37+
c.Assert(assertIsPHPScript(filepath.Join(dir, "unknown")), Equals, false)
38+
c.Assert(assertIsPHPScript(filepath.Join(dir, "invalid")), Equals, false)
3939

4040
for _, validScripts := range []string{
4141
"usual-one",
4242
"debian-style",
4343
"custom-one",
4444
"plain-one.php",
4545
} {
46-
c.Assert(isPHPScript(filepath.Join(dir, validScripts)), Equals, true)
46+
c.Assert(assertIsPHPScript(filepath.Join(dir, validScripts)), Equals, true)
4747
}
4848
}

local/php/composer.go

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
package php
2121

2222
import (
23-
"bufio"
2423
"bytes"
2524
"crypto/sha512"
2625
"encoding/hex"
@@ -77,7 +76,7 @@ func Composer(dir string, args, env []string, stdout, stderr, logger io.Writer,
7776
if composerPath := os.Getenv("SYMFONY_COMPOSER_PATH"); composerPath != "" {
7877
debugLogger.Debug().Str("SYMFONY_COMPOSER_PATH", composerPath).Msg("SYMFONY_COMPOSER_PATH has been defined. User is taking control over Composer detection and execution.")
7978
e.Args = append([]string{composerPath}, args...)
80-
} else if path, err := e.findComposer(composerBin); err == nil && isPHPScript(path) {
79+
} else if path, err := e.findComposer(composerBin); err == nil && assertIsPHPScript(path) {
8180
e.Args = append([]string{"php", path}, args...)
8281
} else {
8382
reason := "No Composer installation found."
@@ -108,29 +107,6 @@ func Composer(dir string, args, env []string, stdout, stderr, logger io.Writer,
108107
return ComposerResult{}
109108
}
110109

111-
// isPHPScript checks that the composer file is indeed a phar/PHP script (not a .bat file)
112-
func isPHPScript(path string) bool {
113-
if path == "" {
114-
return false
115-
}
116-
file, err := os.Open(path)
117-
if err != nil {
118-
return false
119-
}
120-
defer file.Close()
121-
reader := bufio.NewReader(file)
122-
byteSlice, _, err := reader.ReadLine()
123-
if err != nil {
124-
return false
125-
}
126-
127-
if bytes.Equal(byteSlice, []byte("<?php")) {
128-
return true
129-
}
130-
131-
return bytes.HasPrefix(byteSlice, []byte("#!/")) && bytes.HasSuffix(byteSlice, []byte("php"))
132-
}
133-
134110
func composerVersion() int {
135111
var lock struct {
136112
Version string `json:"plugin-api-version"`

local/php/executor.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,26 @@ func (e *Executor) findComposer(extraBin string) (string, error) {
417417
return findComposer(extraBin, e.Logger)
418418
}
419419

420+
// findPie locates the PIE binary depending on the configuration
421+
func (e *Executor) findPie() (string, error) {
422+
if scriptDir, err := e.DetectScriptDir(); err == nil {
423+
for _, file := range []string{"pie.phar", "pie"} {
424+
path := filepath.Join(scriptDir, file)
425+
e.Logger.Debug().Str("source", "PIE").Msgf(`Looking for PIE under "%s"`, path)
426+
d, err := os.Stat(path)
427+
if err != nil {
428+
continue
429+
}
430+
if m := d.Mode(); !m.IsDir() {
431+
e.Logger.Debug().Str("source", "PIE").Msgf(`Found potential PIE as "%s"`, path)
432+
return path, nil
433+
}
434+
}
435+
}
436+
437+
return findPie(e.Logger)
438+
}
439+
420440
// Execute executes the right version of PHP depending on the configuration
421441
func (e *Executor) Execute(loadDotEnv bool) int {
422442
if err := e.Config(loadDotEnv); err != nil {

local/php/pie.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright (c) 2021-present Fabien Potencier <fabien@symfony.com>
3+
*
4+
* This file is part of Symfony CLI project
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package php
21+
22+
import (
23+
"fmt"
24+
"github.com/pkg/errors"
25+
"github.com/rs/zerolog"
26+
"github.com/symfony-cli/symfony-cli/util"
27+
"io"
28+
"net/http"
29+
"os"
30+
"path/filepath"
31+
"strings"
32+
)
33+
34+
type PieResult struct {
35+
code int
36+
error error
37+
}
38+
39+
func (p PieResult) Error() string {
40+
if p.error != nil {
41+
return p.error.Error()
42+
}
43+
44+
return ""
45+
}
46+
47+
func (p PieResult) ExitCode() int {
48+
return p.code
49+
}
50+
51+
func Pie(dir string, args, env []string, stdout, stderr, logger io.Writer, debugLogger zerolog.Logger) PieResult {
52+
e := &Executor{
53+
Dir: dir,
54+
BinName: "php",
55+
Stdout: stdout,
56+
Stderr: stderr,
57+
SkipNbArgs: -1,
58+
ExtraEnv: env,
59+
Logger: debugLogger,
60+
}
61+
62+
if piePath := os.Getenv("SYMFONY_PIE_PATH"); piePath != "" {
63+
debugLogger.Debug().Str("SYMFONY_PIE_PATH", piePath).Msg("SYMFONY_PIE_PATH has been defined. User is taking control over PIE detection and execution.")
64+
e.Args = append([]string{piePath}, args...)
65+
} else if path, err := e.findPie(); err == nil && assertIsPHPScript(path) {
66+
e.Args = append([]string{"php", path}, args...)
67+
} else {
68+
reason := "No PIE installation found."
69+
if path != "" {
70+
reason = fmt.Sprintf("Detected PIE file (%s) is not a valid PHAR or PHP script.", path)
71+
}
72+
fmt.Fprintln(logger, " WARNING:", reason)
73+
fmt.Fprintln(logger, " Downloading PIE for you, but it is recommended to install PIE yourself, instructions available at https://github.com/php/pie")
74+
// we don't store it under bin/ to avoid it being found by findPie as we want to only use it as a fallback
75+
binDir := filepath.Join(util.GetHomeDir(), "pie")
76+
if path, err = downloadPie(binDir); err != nil {
77+
return PieResult{
78+
code: 1,
79+
error: errors.Wrap(err, "unable to find pie, get it at https://github.com/php/pie"),
80+
}
81+
}
82+
e.Args = append([]string{"php", path}, args...)
83+
fmt.Fprintf(logger, " (running %s)\n\n", e.CommandLine())
84+
}
85+
86+
ret := e.Execute(false)
87+
if ret != 0 {
88+
return PieResult{
89+
code: ret,
90+
error: errors.Errorf("unable to run %s", e.CommandLine()),
91+
}
92+
}
93+
return PieResult{}
94+
}
95+
96+
func findPie(logger zerolog.Logger) (string, error) {
97+
for _, file := range []string{"pie", "pie.phar"} {
98+
logger.Debug().Str("source", "PIE").Msgf(`Looking for PIE in the PATH as "%s"`, file)
99+
if pharPath, _ := LookPath(file); pharPath != "" {
100+
// On Windows, we don't want the .bat, but the real pie phar/PHP file
101+
if strings.HasSuffix(pharPath, ".bat") {
102+
pharPath = pharPath[:len(pharPath)-4] + ".phar"
103+
}
104+
logger.Debug().Str("source", "PIE").Msgf(`Found potential PIE as "%s"`, pharPath)
105+
return pharPath, nil
106+
}
107+
}
108+
109+
return "", os.ErrNotExist
110+
}
111+
112+
func downloadPie(dir string) (string, error) {
113+
if err := os.MkdirAll(dir, 0755); err != nil {
114+
return "", err
115+
}
116+
path := filepath.Join(dir, "pie.phar")
117+
if _, err := os.Stat(path); err == nil {
118+
return path, nil
119+
}
120+
121+
piePhar, err := downloadPiePhar()
122+
if err != nil {
123+
return "", err
124+
}
125+
126+
err = os.WriteFile(path, piePhar, 0755)
127+
if err != nil {
128+
return "", err
129+
}
130+
131+
return path, nil
132+
}
133+
134+
func downloadPiePhar() ([]byte, error) {
135+
resp, err := http.Get("https://github.com/php/pie/releases/latest/download/pie.phar")
136+
if err != nil {
137+
return nil, err
138+
}
139+
defer resp.Body.Close()
140+
return io.ReadAll(resp.Body)
141+
}

main.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,19 @@ func main() {
7979
os.Exit(executor.Execute(false))
8080
}
8181
}
82-
// called via "symfony composer"?
83-
if len(args) >= 2 && args[1] == "composer" {
84-
res := php.Composer("", args[2:], getCliExtraEnv(), os.Stdout, os.Stderr, os.Stderr, terminal.Logger)
85-
terminal.Eprintln(res.Error())
86-
os.Exit(res.ExitCode())
82+
// called via "symfony composer" or "symfony pie"?
83+
if len(args) >= 2 {
84+
if args[1] == "composer" {
85+
res := php.Composer("", args[2:], getCliExtraEnv(), os.Stdout, os.Stderr, os.Stderr, terminal.Logger)
86+
terminal.Eprintln(res.Error())
87+
os.Exit(res.ExitCode())
88+
}
89+
90+
if args[1] == "pie" {
91+
res := php.Pie("", args[2:], getCliExtraEnv(), os.Stdout, os.Stderr, os.Stderr, terminal.Logger)
92+
terminal.Eprintln(res.Error())
93+
os.Exit(res.ExitCode())
94+
}
8795
}
8896

8997
for _, env := range []string{"BRANCH", "ENV", "APPLICATION_NAME"} {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy