Skip to content

Commit 89e84de

Browse files
Daniel MikusaForestEckhardt
authored andcommitted
rust-toolchain.toml support
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 <dmikusa@vmware.com>
1 parent 8076e76 commit 89e84de

6 files changed

Lines changed: 277 additions & 44 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ The buildpack will do the following:
1414
* Contributes `rustup-init` to a layer marked `cache` with command on `$PATH`
1515
* Executes `rustup-init` with the output written to a layer marked `build` and `cache` with installed commands on `$PATH`
1616
* Executes `rustup` to install a Rust toolchain to a layer marked `build` and `cache` with installed commands on `$PATH`
17+
* 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.
18+
* If `rust-toolchain` or `rust-toolchain.toml` do not exist, `rustup` will install `$BP_RUST_TOOLCHAIN` / `$BP_RUST_PROFILE`.
1719
* If `$BP_RUST_TARGET` is set, executes `rustup target add` to install an additional Rust target.
1820
* 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.
1921

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ require (
2323
github.com/pelletier/go-toml v1.9.5 // indirect
2424
github.com/pmezard/go-difflib v1.0.0 // indirect
2525
github.com/stretchr/objx v0.4.0 // indirect
26-
golang.org/x/net v0.0.0-20220524220425-1d687d428aca // indirect
27-
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
26+
golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect
27+
golang.org/x/sys v0.0.0-20220702020025-31831981b65f // indirect
2828
golang.org/x/text v0.3.7 // indirect
2929
gopkg.in/yaml.v2 v2.4.0 // indirect
3030
gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
9090
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
9191
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
9292
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
93-
golang.org/x/net v0.0.0-20220524220425-1d687d428aca h1:xTaFYiPROfpPhqrfTIDXj0ri1SpfueYT951s4bAuDO8=
94-
golang.org/x/net v0.0.0-20220524220425-1d687d428aca/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
93+
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
94+
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
9595
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
9696
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
9797
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -113,8 +113,9 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
113113
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
114114
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
115115
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
116-
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
117116
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
117+
golang.org/x/sys v0.0.0-20220702020025-31831981b65f h1:xdsejrW/0Wf2diT5CPp3XmKUNbr7Xvw8kYilQ+6qjRY=
118+
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
118119
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
119120
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
120121
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

rustup/build.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
package rustup
1818

1919
import (
20+
"errors"
2021
"fmt"
22+
"io/fs"
23+
"os"
24+
"path/filepath"
2125
"runtime"
2226

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

8387
// install rustup
84-
profile, _ := cr.Resolve("BP_RUST_PROFILE")
88+
profile, profileSet := cr.Resolve("BP_RUST_PROFILE")
8589
rustup := NewRustup(rustupInitDependency.Version, profile)
8690
rustup.Logger = b.Logger
8791

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

9094
// install rust
91-
rustVersion, _ := cr.Resolve("BP_RUST_TOOLCHAIN")
95+
rustToolChainFilePath, err := rustToolChainFilePath(context.Application.Path)
96+
if err != nil {
97+
return libcnb.BuildResult{}, fmt.Errorf("unable to find dependency\n%w", err)
98+
}
99+
100+
rustVersion, rustVersionSet := cr.Resolve("BP_RUST_TOOLCHAIN")
92101
additionalTarget := AdditionalTarget(cr, context.StackID)
93-
rust := NewRust(profile, rustVersion, additionalTarget)
102+
rust := NewRust(profile, rustVersion, additionalTarget, rustToolChainFilePath, profileSet, rustVersionSet)
94103
rust.Logger = b.Logger
95104

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

118127
return fmt.Sprintf("%s-unknown-linux-%s", arch, libc)
119128
}
129+
130+
func rustToolChainFilePath(appPath string) (string, error) {
131+
toolchainFilePath := filepath.Join(appPath, "rust-toolchain")
132+
if _, err := os.Stat(toolchainFilePath); err == nil {
133+
return toolchainFilePath, nil
134+
} else if err != nil && !errors.Is(err, fs.ErrNotExist) {
135+
return "", err
136+
}
137+
138+
toolchainFilePath = filepath.Join(appPath, "rust-toolchain.toml")
139+
if _, err := os.Stat(toolchainFilePath); err == nil {
140+
return toolchainFilePath, nil
141+
} else if err != nil && !errors.Is(err, fs.ErrNotExist) {
142+
return "", err
143+
}
144+
145+
return "", nil
146+
}

rustup/rust.go

Lines changed: 116 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/paketo-buildpacks/libpak/bard"
2929
"github.com/paketo-buildpacks/libpak/effect"
3030
"github.com/paketo-buildpacks/libpak/sbom"
31+
"github.com/paketo-buildpacks/libpak/sherpa"
3132
)
3233

3334
// Rust will run `rustup` from the PATH to install a given toolchain
@@ -37,11 +38,14 @@ type Rust struct {
3738
Arguments []string
3839
Executor effect.Executor
3940
Toolchain string
41+
ToolchainSet bool
4042
Target string
4143
Profile string
44+
ProfileSet bool
45+
ToolchainFile string
4246
}
4347

44-
func NewRust(profile, toolchain, target string) Rust {
48+
func NewRust(profile, toolchain, target, toolchainFile string, profileSet, toolchainSet bool) Rust {
4549
return Rust{
4650
LayerContributor: libpak.NewLayerContributor(
4751
"Rust",
@@ -54,10 +58,13 @@ func NewRust(profile, toolchain, target string) Rust {
5458
Build: true,
5559
Cache: true,
5660
}),
57-
Executor: effect.NewExecutor(),
58-
Profile: profile,
59-
Toolchain: toolchain,
60-
Target: target,
61+
Executor: effect.NewExecutor(),
62+
Profile: profile,
63+
ProfileSet: profileSet,
64+
Target: target,
65+
Toolchain: toolchain,
66+
ToolchainSet: toolchainSet,
67+
ToolchainFile: toolchainFile,
6168
}
6269
}
6370

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

86+
// add hash of rust toolchain file (rust-toolchain.toml or rust-toolchain) so it re-runs if that file changes
87+
if hash, err := sherpa.NewFileListingHash(r.ToolchainFile); err != nil {
88+
return libcnb.Layer{}, fmt.Errorf("unable to hash: %s\n%w", r.ToolchainFile, err)
89+
} else {
90+
r.LayerContributor.ExpectedMetadata.(map[string]interface{})["rust-toolchain"] = hash
91+
}
92+
7993
layer, err := r.LayerContributor.Contribute(layer, func() (libcnb.Layer, error) {
8094
r.Logger.Body("Installing Rust")
8195

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

99-
if err := r.Executor.Execute(effect.Execution{
100-
Command: "rustup",
101-
Args: []string{
102-
"-q",
103-
"toolchain",
104-
"install",
105-
fmt.Sprintf("--profile=%s", r.Profile),
106-
r.Toolchain,
107-
},
108-
Dir: layer.Path,
109-
Stdout: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
110-
Stderr: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
111-
}); err != nil {
112-
return libcnb.Layer{}, fmt.Errorf("unable to run `rustup`\n%w", err)
113+
rustToolChainFileExists := false
114+
if _, err := os.Stat(r.ToolchainFile); err == nil {
115+
rustToolChainFileExists = true
113116
}
114117

115-
if r.Target != "" {
116-
if err := r.Executor.Execute(effect.Execution{
117-
Command: "rustup",
118-
Args: []string{
119-
"-q",
120-
"target",
121-
"add",
122-
fmt.Sprintf("--toolchain=%s", r.Toolchain),
123-
r.Target,
124-
},
125-
Dir: layer.Path,
126-
Stdout: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
127-
Stderr: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
128-
}); err != nil {
129-
return libcnb.Layer{}, fmt.Errorf("unable to run `rustup`\n%w", err)
118+
if rustToolChainFileExists {
119+
if err := r.installFromRustToolChainFile(layer); err != nil {
120+
return libcnb.Layer{}, fmt.Errorf("unable to install rust from toolchain file\n%w", err)
121+
}
122+
}
123+
124+
if !rustToolChainFileExists || r.ProfileSet || r.ToolchainSet {
125+
if err := r.installRust(layer); err != nil {
126+
return libcnb.Layer{}, fmt.Errorf("unable to install rust\n%w", err)
130127
}
131128
}
132129

130+
if err := r.installAdditionalTarget(layer); err != nil {
131+
return libcnb.Layer{}, fmt.Errorf("unable to install additional rust target\n%w", err)
132+
}
133+
133134
buf := &bytes.Buffer{}
134135
if err := r.Executor.Execute(effect.Execution{
135136
Command: "rustc",
@@ -180,9 +181,90 @@ func (r Rust) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
180181
}
181182
layer.Metadata["installed"] = strings.TrimSpace(buf.String())
182183

184+
if hash, err := sherpa.NewFileListingHash(r.ToolchainFile); err != nil {
185+
return libcnb.Layer{}, fmt.Errorf("unable to hash: %s\n%w", r.ToolchainFile, err)
186+
} else {
187+
layer.Metadata["rust-toolchain"] = hash
188+
}
189+
183190
return layer, nil
184191
}
185192

186193
func (r Rust) Name() string {
187194
return r.LayerContributor.Name
188195
}
196+
197+
func (r Rust) installRust(layer libcnb.Layer) error {
198+
if err := r.Executor.Execute(effect.Execution{
199+
Command: "rustup",
200+
Args: []string{
201+
"-q",
202+
"toolchain",
203+
"install",
204+
fmt.Sprintf("--profile=%s", r.Profile),
205+
r.Toolchain,
206+
},
207+
Dir: layer.Path,
208+
Stdout: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
209+
Stderr: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
210+
}); err != nil {
211+
return fmt.Errorf("unable to run `rustup toolchain install`\n%w", err)
212+
}
213+
214+
return nil
215+
}
216+
217+
func (r Rust) installAdditionalTarget(layer libcnb.Layer) error {
218+
if r.Target != "" {
219+
if err := r.Executor.Execute(effect.Execution{
220+
Command: "rustup",
221+
Args: []string{
222+
"-q",
223+
"target",
224+
"add",
225+
fmt.Sprintf("--toolchain=%s", r.Toolchain),
226+
r.Target,
227+
},
228+
Dir: layer.Path,
229+
Stdout: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
230+
Stderr: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
231+
}); err != nil {
232+
return fmt.Errorf("unable to run `rustup target add`\n%w", err)
233+
}
234+
}
235+
236+
return nil
237+
}
238+
239+
func (r Rust) installFromRustToolChainFile(layer libcnb.Layer) error {
240+
if err := r.Executor.Execute(effect.Execution{
241+
Command: "rustup",
242+
Args: []string{
243+
"-q",
244+
"default",
245+
r.Toolchain,
246+
},
247+
Dir: layer.Path,
248+
Stdout: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
249+
Stderr: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
250+
}); err != nil {
251+
return fmt.Errorf("unable to run `rustup default`\n%w", err)
252+
}
253+
254+
// This seems weird, but `rustup show` will actually read rust-toolchain.toml or rust-toolchain
255+
// and install anything missing.
256+
if err := r.Executor.Execute(effect.Execution{
257+
Command: "rustup",
258+
Args: []string{
259+
"-q",
260+
"show",
261+
},
262+
Dir: layer.Path,
263+
Stdout: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
264+
Stderr: bard.NewWriter(r.Logger.Logger.InfoWriter(), bard.WithIndent(3)),
265+
}); err != nil {
266+
return fmt.Errorf("unable to run `rustup show`\n%w", err)
267+
}
268+
269+
return nil
270+
}

0 commit comments

Comments
 (0)