Skip to content

Commit

Permalink
rust-toolchain.toml support
Browse files Browse the repository at this point in the history
This PR will optionally install Rust as specified in a 'rust-toolchain.toml' file. If a 'rust-toolchain' file is present, that file is used first. If not present, then 'rust-toolchain.toml' will be used. If both are present then 'rust-toolchain' is used.

When a 'rust-toolchain' or 'rust-toolchain.toml' is included, then the buildpack will run 'rustup' and install Rust as specified in the file. If 'BP_RUST_PROFILE' or 'BP_RUST_TOOLCHAIN' are also set (non-default values), the buildpack will install them as well. If 'rust-toolchain' or 'rust-toolchain.toml' are not present then the buildpack will install 'BP_RUST_PROFILE' or 'BP_RUST_TOOLCHAIN'.

The additional target as set in 'BP_RUST_TARGET' is still installed, as this is necessary if you are running on the Tiny stack.

Resolves #56

Signed-off-by: Daniel Mikusa <[email protected]>
  • Loading branch information
Daniel Mikusa authored and ForestEckhardt committed Jul 6, 2022
1 parent 8076e76 commit 89e84de
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 44 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ The buildpack will do the following:
* Contributes `rustup-init` to a layer marked `cache` with command on `$PATH`
* Executes `rustup-init` with the output written to a layer marked `build` and `cache` with installed commands on `$PATH`
* Executes `rustup` to install a Rust toolchain to a layer marked `build` and `cache` with installed commands on `$PATH`
* If `rust-toolchain` or `rust-toolchain.toml` exists, `rustup` will install as configured in the file. If `$BP_RUST_TOOLCHAIN` / `$BP_RUST_PROFILE` are also set to non-default values, they will also be installed.
* If `rust-toolchain` or `rust-toolchain.toml` do not exist, `rustup` will install `$BP_RUST_TOOLCHAIN` / `$BP_RUST_PROFILE`.
* If `$BP_RUST_TARGET` is set, executes `rustup target add` to install an additional Rust target.
* If `$BP_RUST_TARGET` is not set and the build is running on the Paketo Tiny stack, then the Rust Linux musl target will be automatically added.

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ require (
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.4.0 // indirect
golang.org/x/net v0.0.0-20220524220425-1d687d428aca // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect
golang.org/x/sys v0.0.0-20220702020025-31831981b65f // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
7 changes: 4 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220524220425-1d687d428aca h1:xTaFYiPROfpPhqrfTIDXj0ri1SpfueYT951s4bAuDO8=
golang.org/x/net v0.0.0-20220524220425-1d687d428aca/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand All @@ -113,8 +113,9 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220702020025-31831981b65f h1:xdsejrW/0Wf2diT5CPp3XmKUNbr7Xvw8kYilQ+6qjRY=
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
33 changes: 30 additions & 3 deletions rustup/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
package rustup

import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"runtime"

"github.com/buildpacks/libcnb"
Expand Down Expand Up @@ -81,16 +85,21 @@ func (b Build) Build(context libcnb.BuildContext) (libcnb.BuildResult, error) {
result.Layers = append(result.Layers, cargo)

// install rustup
profile, _ := cr.Resolve("BP_RUST_PROFILE")
profile, profileSet := cr.Resolve("BP_RUST_PROFILE")
rustup := NewRustup(rustupInitDependency.Version, profile)
rustup.Logger = b.Logger

result.Layers = append(result.Layers, rustup)

// install rust
rustVersion, _ := cr.Resolve("BP_RUST_TOOLCHAIN")
rustToolChainFilePath, err := rustToolChainFilePath(context.Application.Path)
if err != nil {
return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err)
}

rustVersion, rustVersionSet := cr.Resolve("BP_RUST_TOOLCHAIN")
additionalTarget := AdditionalTarget(cr, context.StackID)
rust := NewRust(profile, rustVersion, additionalTarget)
rust := NewRust(profile, rustVersion, additionalTarget, rustToolChainFilePath, profileSet, rustVersionSet)
rust.Logger = b.Logger

result.Layers = append(result.Layers, rust)
Expand All @@ -117,3 +126,21 @@ func AdditionalTarget(cr libpak.ConfigurationResolver, stack string) string {

return fmt.Sprintf("%s-unknown-linux-%s", arch, libc)
}

func rustToolChainFilePath(appPath string) (string, error) {
toolchainFilePath := filepath.Join(appPath, "rust-toolchain")
if _, err := os.Stat(toolchainFilePath); err == nil {
return toolchainFilePath, nil
} else if err != nil && !errors.Is(err, fs.ErrNotExist) {
return "", err
}

toolchainFilePath = filepath.Join(appPath, "rust-toolchain.toml")
if _, err := os.Stat(toolchainFilePath); err == nil {
return toolchainFilePath, nil
} else if err != nil && !errors.Is(err, fs.ErrNotExist) {
return "", err
}

return "", nil
}
150 changes: 116 additions & 34 deletions rustup/rust.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/paketo-buildpacks/libpak/bard"
"github.com/paketo-buildpacks/libpak/effect"
"github.com/paketo-buildpacks/libpak/sbom"
"github.com/paketo-buildpacks/libpak/sherpa"
)

// Rust will run `rustup` from the PATH to install a given toolchain
Expand All @@ -37,11 +38,14 @@ type Rust struct {
Arguments []string
Executor effect.Executor
Toolchain string
ToolchainSet bool
Target string
Profile string
ProfileSet bool
ToolchainFile string
}

func NewRust(profile, toolchain, target string) Rust {
func NewRust(profile, toolchain, target, toolchainFile string, profileSet, toolchainSet bool) Rust {
return Rust{
LayerContributor: libpak.NewLayerContributor(
"Rust",
Expand All @@ -54,10 +58,13 @@ func NewRust(profile, toolchain, target string) Rust {
Build: true,
Cache: true,
}),
Executor: effect.NewExecutor(),
Profile: profile,
Toolchain: toolchain,
Target: target,
Executor: effect.NewExecutor(),
Profile: profile,
ProfileSet: profileSet,
Target: target,
Toolchain: toolchain,
ToolchainSet: toolchainSet,
ToolchainFile: toolchainFile,
}
}

Expand All @@ -76,6 +83,13 @@ func (r Rust) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
}
r.LayerContributor.ExpectedMetadata.(map[string]interface{})["installed"] = strings.TrimSpace(buf.String())

// add hash of rust toolchain file (rust-toolchain.toml or rust-toolchain) so it re-runs if that file changes
if hash, err := sherpa.NewFileListingHash(r.ToolchainFile); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to hash: %s\n%w", r.ToolchainFile, err)
} else {
r.LayerContributor.ExpectedMetadata.(map[string]interface{})["rust-toolchain"] = hash
}

layer, err := r.LayerContributor.Contribute(layer, func() (libcnb.Layer, error) {
r.Logger.Body("Installing Rust")

Expand All @@ -96,40 +110,27 @@ func (r Rust) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
}
}

if err := r.Executor.Execute(effect.Execution{
Command: "rustup",
Args: []string{
"-q",
"toolchain",
"install",
fmt.Sprintf("--profile=%s", r.Profile),
r.Toolchain,
},
Dir: layer.Path,
Stdout: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
Stderr: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
}); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to run `rustup`\n%w", err)
rustToolChainFileExists := false
if _, err := os.Stat(r.ToolchainFile); err == nil {
rustToolChainFileExists = true
}

if r.Target != "" {
if err := r.Executor.Execute(effect.Execution{
Command: "rustup",
Args: []string{
"-q",
"target",
"add",
fmt.Sprintf("--toolchain=%s", r.Toolchain),
r.Target,
},
Dir: layer.Path,
Stdout: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
Stderr: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
}); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to run `rustup`\n%w", err)
if rustToolChainFileExists {
if err := r.installFromRustToolChainFile(layer); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to install rust from toolchain file\n%w", err)
}
}

if !rustToolChainFileExists || r.ProfileSet || r.ToolchainSet {
if err := r.installRust(layer); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to install rust\n%w", err)
}
}

if err := r.installAdditionalTarget(layer); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to install additional rust target\n%w", err)
}

buf := &bytes.Buffer{}
if err := r.Executor.Execute(effect.Execution{
Command: "rustc",
Expand Down Expand Up @@ -180,9 +181,90 @@ func (r Rust) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
}
layer.Metadata["installed"] = strings.TrimSpace(buf.String())

if hash, err := sherpa.NewFileListingHash(r.ToolchainFile); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to hash: %s\n%w", r.ToolchainFile, err)
} else {
layer.Metadata["rust-toolchain"] = hash
}

return layer, nil
}

func (r Rust) Name() string {
return r.LayerContributor.Name
}

func (r Rust) installRust(layer libcnb.Layer) error {
if err := r.Executor.Execute(effect.Execution{
Command: "rustup",
Args: []string{
"-q",
"toolchain",
"install",
fmt.Sprintf("--profile=%s", r.Profile),
r.Toolchain,
},
Dir: layer.Path,
Stdout: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
Stderr: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
}); err != nil {
return fmt.Errorf("unable to run `rustup toolchain install`\n%w", err)
}

return nil
}

func (r Rust) installAdditionalTarget(layer libcnb.Layer) error {
if r.Target != "" {
if err := r.Executor.Execute(effect.Execution{
Command: "rustup",
Args: []string{
"-q",
"target",
"add",
fmt.Sprintf("--toolchain=%s", r.Toolchain),
r.Target,
},
Dir: layer.Path,
Stdout: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
Stderr: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
}); err != nil {
return fmt.Errorf("unable to run `rustup target add`\n%w", err)
}
}

return nil
}

func (r Rust) installFromRustToolChainFile(layer libcnb.Layer) error {
if err := r.Executor.Execute(effect.Execution{
Command: "rustup",
Args: []string{
"-q",
"default",
r.Toolchain,
},
Dir: layer.Path,
Stdout: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
Stderr: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
}); err != nil {
return fmt.Errorf("unable to run `rustup default`\n%w", err)
}

// This seems weird, but `rustup show` will actually read rust-toolchain.toml or rust-toolchain
// and install anything missing.
if err := r.Executor.Execute(effect.Execution{
Command: "rustup",
Args: []string{
"-q",
"show",
},
Dir: layer.Path,
Stdout: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
Stderr: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
}); err != nil {
return fmt.Errorf("unable to run `rustup show`\n%w", err)
}

return nil
}
Loading

0 comments on commit 89e84de

Please sign in to comment.