From f07fedc46d55ad5d0c43a317adce20d0886b49df Mon Sep 17 00:00:00 2001 From: Joel Demars Date: Mon, 23 Feb 2026 18:34:58 -0700 Subject: [PATCH 1/3] Add PGO support --- README.md | 3 +++ builder.go | 11 +++++++++++ cmd/commands.go | 20 +++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e56c56b..638675c 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ $ xcaddy build [] [--with ...] [--replace ...] [--embed <[alias]:path/to/dir>...] + [--pgo ] # EXPERIMENTAL ``` - `` is the core Caddy version to build; defaults to `CADDY_VERSION` env variable or latest.
@@ -80,6 +81,8 @@ $ xcaddy build [] - `--embed` can be used to embed the contents of a directory into the Caddy executable. `--embed` can be passed multiple times with separate source directories. The source directory can be prefixed with a custom alias and a colon `:` to write the embedded files into an aliased subdirectory, which is useful when combined with the `root` directive and sub-directive. +- `--pgo` can be used to specify a file containing a profile to use for profile guided optimization. If a file named `default.pgo` is present in the current directory, it will automatically be used. This feature is new to xcaddy and is considered experimental. + #### Examples ```bash diff --git a/builder.go b/builder.go index b2c86ed..d84dddb 100644 --- a/builder.go +++ b/builder.go @@ -47,6 +47,7 @@ type Builder struct { Debug bool `json:"debug,omitempty"` BuildFlags string `json:"build_flags,omitempty"` ModFlags string `json:"mod_flags,omitempty"` + PgoProfile string `json:"pgo_profile,omitempty"` // Experimental: subject to change EmbedDirs []struct { @@ -76,6 +77,12 @@ func (b Builder) Build(ctx context.Context, outputFile string) error { } log.Printf("[INFO] absolute output file path: %s", absOutputFile) + // likewise with the PGO profile + absPgoProfile, err := filepath.Abs(b.PgoProfile) + if err != nil { + return err + } + // set some defaults from the environment, if applicable if b.OS == "" { b.OS = utils.GetGOOS() @@ -153,6 +160,10 @@ func (b Builder) Build(ctx context.Context, outputFile string) error { cmd, err := buildEnv.newGoBuildCommand(ctx, "build", "-o", absOutputFile, ) + if b.PgoProfile != "" { + log.Printf("[INFO] using PGO profile %s", b.PgoProfile) + cmd.Args = append(cmd.Args, "-pgo=" + absPgoProfile) + } if err != nil { return err } diff --git a/cmd/commands.go b/cmd/commands.go index 9670375..7f0765a 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -19,6 +19,7 @@ func init() { buildCommand.Flags().String("output", "", "change the output file name") buildCommand.Flags().StringArray("replace", []string{}, "like --with but for Go modules") buildCommand.Flags().StringArray("embed", []string{}, "embeds directories into the built Caddy executable to use with the `embedded` file-system") + buildCommand.Flags().String("pgo", "", "file containing profile for PGO (experimental)") } var versionCommand = &cobra.Command{ @@ -35,7 +36,8 @@ var buildCommand = &cobra.Command{ [--output ] [--with ...] [--replace ...] - [--embed <[alias]:path/to/dir>...]`, + [--embed <[alias]:path/to/dir>...] + [--pgo ] # EXPERIMENTAL`, Long: ` is the core Caddy version to build; defaults to CADDY_VERSION env variable or latest. This can be the keyword latest, which will use the latest stable tag, or any git ref such as: @@ -52,6 +54,8 @@ Flags: --replace is like --with, but does not add a blank import to the code; it only writes a replace directive to go.mod, which is useful when developing on Caddy's dependencies (ones that are not Caddy modules). Try this if you got an error when using --with, like cannot find module providing package. --embed can be used to embed the contents of a directory into the Caddy executable. --embed can be passed multiple times with separate source directories. The source directory can be prefixed with a custom alias and a colon : to write the embedded files into an aliased subdirectory, which is useful when combined with the root directive and sub-directive. + + --pgo is used to specify a specify a file containing a profile for profile-guided optimization (experimental) `, Short: "Compile custom caddy binaries", Args: cobra.MaximumNArgs(1), @@ -60,6 +64,7 @@ Flags: var plugins []xcaddy.Dependency var replacements []xcaddy.Replace var embedDir []string + var pgo string var argCaddyVersion string if len(args) > 0 { argCaddyVersion = args[0] @@ -103,6 +108,18 @@ Flags: if err != nil { return fmt.Errorf("unable to parse --embed arguments: %s", err.Error()) } + + pgo, err = cmd.Flags().GetString("pgo") + if err != nil { + return fmt.Errorf("unable to parse --pgo arguments: %s", err.Error()) + } + if pgo == "" { + _, err = os.Stat("default.pgo") + if err == nil { + pgo = "default.pgo" + } + } + // prefer caddy version from command line argument over env var if argCaddyVersion != "" { caddyVersion = argCaddyVersion @@ -127,6 +144,7 @@ Flags: Debug: buildDebugOutput, BuildFlags: buildFlags, ModFlags: modFlags, + PgoProfile: pgo, } for _, md := range embedDir { if before, after, found := strings.Cut(md, ":"); found { From a1f4df8e5b91ba48b875f37328e5701d87a7cdf2 Mon Sep 17 00:00:00 2001 From: Joel Demars Date: Tue, 24 Feb 2026 13:01:21 -0700 Subject: [PATCH 2/3] Add "Experimental" comments to code --- builder.go | 2 +- cmd/commands.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/builder.go b/builder.go index d84dddb..de43856 100644 --- a/builder.go +++ b/builder.go @@ -47,7 +47,7 @@ type Builder struct { Debug bool `json:"debug,omitempty"` BuildFlags string `json:"build_flags,omitempty"` ModFlags string `json:"mod_flags,omitempty"` - PgoProfile string `json:"pgo_profile,omitempty"` + PgoProfile string `json:"pgo_profile,omitempty"` // Experimental // Experimental: subject to change EmbedDirs []struct { diff --git a/cmd/commands.go b/cmd/commands.go index 7f0765a..7f4fd5f 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -109,6 +109,7 @@ Flags: return fmt.Errorf("unable to parse --embed arguments: %s", err.Error()) } + // Experimental: If no --pgo flag is specified and a default.pgo file is found, use that for PGO pgo, err = cmd.Flags().GetString("pgo") if err != nil { return fmt.Errorf("unable to parse --pgo arguments: %s", err.Error()) From 5c919efe132109d88d61da7cca2dbf62550d695b Mon Sep 17 00:00:00 2001 From: Joel Demars Date: Mon, 16 Mar 2026 19:08:00 -0600 Subject: [PATCH 3/3] Fix formatting --- builder.go | 6 +++--- cmd/commands.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/builder.go b/builder.go index de43856..a0cbe48 100644 --- a/builder.go +++ b/builder.go @@ -47,7 +47,7 @@ type Builder struct { Debug bool `json:"debug,omitempty"` BuildFlags string `json:"build_flags,omitempty"` ModFlags string `json:"mod_flags,omitempty"` - PgoProfile string `json:"pgo_profile,omitempty"` // Experimental + PgoProfile string `json:"pgo_profile,omitempty"` // Experimental // Experimental: subject to change EmbedDirs []struct { @@ -82,7 +82,7 @@ func (b Builder) Build(ctx context.Context, outputFile string) error { if err != nil { return err } - + // set some defaults from the environment, if applicable if b.OS == "" { b.OS = utils.GetGOOS() @@ -162,7 +162,7 @@ func (b Builder) Build(ctx context.Context, outputFile string) error { ) if b.PgoProfile != "" { log.Printf("[INFO] using PGO profile %s", b.PgoProfile) - cmd.Args = append(cmd.Args, "-pgo=" + absPgoProfile) + cmd.Args = append(cmd.Args, "-pgo="+absPgoProfile) } if err != nil { return err diff --git a/cmd/commands.go b/cmd/commands.go index 7f4fd5f..cf94adc 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -108,7 +108,7 @@ Flags: if err != nil { return fmt.Errorf("unable to parse --embed arguments: %s", err.Error()) } - + // Experimental: If no --pgo flag is specified and a default.pgo file is found, use that for PGO pgo, err = cmd.Flags().GetString("pgo") if err != nil { @@ -120,7 +120,7 @@ Flags: pgo = "default.pgo" } } - + // prefer caddy version from command line argument over env var if argCaddyVersion != "" { caddyVersion = argCaddyVersion