Skip to content

Commit c128ba4

Browse files
committed
feat: add rust feature
1 parent bd71d50 commit c128ba4

12 files changed

Lines changed: 397 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jobs:
3535
"node",
3636
"playwright-deps",
3737
"python",
38+
"rust",
3839
"sonar-scanner-cli",
3940
"system-packages",
4041
"timezone",

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Below is a list with included features, click on the link for more details.
3535
| [node](./features/src/node/README.md) | Installs Node.js. |
3636
| [playwright-deps](./features/src/playwright-deps/README.md) | Installs all dependencies required to run Playwright. |
3737
| [python](./features/src/python/README.md) | Installs Python. |
38+
| [rust](./features/src/rust/README.md) | A package which installs Rust. |
3839
| [sonar-scanner-cli](./features/src/sonar-scanner-cli/README.md) | Installs the SonarScanner CLI. |
3940
| [system-packages](./features/src/system-packages/README.md) | Install arbitrary system packages using the system package manager. |
4041
| [timezone](./features/src/timezone/README.md) | Allows setting the timezone. |

build/build.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ func init() {
180180
gotaskr.Task("Feature:python:Test", func() error { return testFeature("python") })
181181
gotaskr.Task("Feature:python:Publish", func() error { return publishFeature("python") })
182182

183+
////////// rust
184+
gotaskr.Task("Feature:rust:Package", func() error { return packageFeature("rust") })
185+
gotaskr.Task("Feature:rust:Test", func() error { return testFeature("rust") })
186+
gotaskr.Task("Feature:rust:Publish", func() error { return publishFeature("rust") })
187+
183188
////////// sonar-scanner-cli
184189
gotaskr.Task("Feature:sonar-scanner-cli:Package", func() error { return packageFeature("sonar-scanner-cli") })
185190
gotaskr.Task("Feature:sonar-scanner-cli:Test", func() error { return testFeature("sonar-scanner-cli") })

features/src/rust/NOTES.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## Notes
2+
3+
### System Compatibility
4+
5+
Debian, Ubuntu
6+
7+
### Accessed Urls
8+
9+
Needs access to the following URL for downloading and resolving:
10+
* https://static.rust-lang.org

features/src/rust/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Rust (rust)
2+
3+
A package which installs Rust.
4+
5+
## Example Usage
6+
7+
```json
8+
"features": {
9+
"ghcr.io/postfinance/devcontainer-features/rust:0.1.0": {
10+
"version": "latest",
11+
"rustupVersion": "latest",
12+
"profile": "minimal",
13+
"components": "rustfmt,rust-analyzer,rust-src,clippy",
14+
"enableWindowsTarget": false,
15+
"rustupDownloadUrl": ""
16+
}
17+
}
18+
```
19+
20+
## Options
21+
22+
| Option | Description | Type | Default Value | Proposals |
23+
|-----|-----|-----|-----|-----|
24+
| version | The version of Rust to install. | string | latest | latest, 1.76 |
25+
| rustupVersion | The version of rustup to install. | string | latest | latest, 1.27.1 |
26+
| profile | The rustup profile to install. | string | minimal | minimal, default, complete |
27+
| components | A comma separated list with components that should be installed. | string | rustfmt,rust-analyzer,rust-src,clippy | , rustfmt,rust-analyzer, rls,rust-analysis |
28+
| enableWindowsTarget | A flag to indicate if the Windows target (and needed tools) should be installed. | boolean | false | true, false |
29+
| rustupDownloadUrl | The download URL for rustup. If not set, the default URL will be used. | string | <empty> | https://static.rust-lang.org/rustup/archive |
30+
31+
## Customizations
32+
33+
### VS Code Extensions
34+
35+
- `vadimcn.vscode-lldb`
36+
- `rust-lang.rust-analyzer`
37+
- `tamasfe.even-better-toml`
38+
- `serayuzgur.crates`
39+
40+
## Notes
41+
42+
### System Compatibility
43+
44+
Debian, Ubuntu
45+
46+
### Accessed Urls
47+
48+
Needs access to the following URL for downloading and resolving:
49+
* https://static.rust-lang.org
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
{
2+
"id": "rust",
3+
"version": "0.1.0",
4+
"name": "Rust",
5+
"description": "A package which installs Rust.",
6+
"options": {
7+
"version": {
8+
"type": "string",
9+
"proposals": [
10+
"latest",
11+
"1.76"
12+
],
13+
"default": "latest",
14+
"description": "The version of Rust to install."
15+
},
16+
"rustupVersion": {
17+
"type": "string",
18+
"proposals": [
19+
"latest",
20+
"1.27.1"
21+
],
22+
"default": "latest",
23+
"description": "The version of rustup to install."
24+
},
25+
"profile": {
26+
"type": "string",
27+
"proposals": [
28+
"minimal",
29+
"default",
30+
"complete"
31+
],
32+
"default": "minimal",
33+
"description": "The rustup profile to install."
34+
},
35+
"components": {
36+
"type": "string",
37+
"proposals": [
38+
"",
39+
"rustfmt,rust-analyzer",
40+
"rls,rust-analysis"
41+
],
42+
"default": "rustfmt,rust-analyzer,rust-src,clippy",
43+
"description": "A comma separated list with components that should be installed."
44+
},
45+
"enableWindowsTarget": {
46+
"type": "boolean",
47+
"default": false,
48+
"description": "A flag to indicate if the Windows target (and needed tools) should be installed."
49+
},
50+
"rustupDownloadUrl": {
51+
"type": "string",
52+
"default": "",
53+
"description": "The download URL for rustup. If not set, the default URL will be used.",
54+
"proposals": [
55+
"https://static.rust-lang.org/rustup/archive"
56+
]
57+
}
58+
},
59+
"customizations": {
60+
"vscode": {
61+
"extensions": [
62+
"vadimcn.vscode-lldb",
63+
"rust-lang.rust-analyzer",
64+
"tamasfe.even-better-toml",
65+
"serayuzgur.crates"
66+
]
67+
}
68+
},
69+
"containerEnv": {
70+
"CARGO_HOME": "/usr/local/cargo",
71+
"RUSTUP_HOME": "/usr/local/rustup",
72+
"PATH": "/usr/local/cargo/bin:${PATH}"
73+
}
74+
}

features/src/rust/install.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
. ./functions.sh
2+
3+
"./installer_$(detect_arch)" \
4+
-version="${VERSION:-"latest"}" \
5+
-rustupVersion="${RUSTUPVERSION:-"latest"}" \
6+
-rustupDownloadUrl="${RUSTUPDOWNLOADURL:-""}" \
7+
-profile="${PROFILE:-"minimal"}" \
8+
-components="${COMPONENTS:-"rustfmt,rust-analyzer,rust-src,clippy"}" \
9+
-enableWindowsTarget="${ENABLEWINDOWSTARGET:-"false"}"

features/src/rust/installer.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package main
2+
3+
import (
4+
"builder/installer"
5+
"flag"
6+
"fmt"
7+
"os"
8+
"regexp"
9+
"strings"
10+
11+
"github.com/roemer/gotaskr/execr"
12+
"github.com/roemer/gover"
13+
)
14+
15+
//////////
16+
// Configuration
17+
//////////
18+
19+
// Regex with 2-3 digits like 1.0 or 1.79.0
20+
var threeDigitRegex *regexp.Regexp = regexp.MustCompile(`^?(\d+)\.(\d+)(?:\.(\d+))?$`)
21+
22+
// Full Regex versioning, like 1.0.0-alpha.2
23+
var semVerRegex *regexp.Regexp = regexp.MustCompile(`^?(\d+)\.(\d+)(?:\.(\d+))?(?:-([a-z]+)(?:\.?(\d+))?)?$`)
24+
25+
//////////
26+
// Main
27+
//////////
28+
29+
func main() {
30+
if err := runMain(); err != nil {
31+
fmt.Printf("Error: %v\n", err)
32+
os.Exit(1)
33+
}
34+
}
35+
36+
func runMain() error {
37+
// Handle the flags
38+
version := flag.String("version", "lts", "")
39+
rustupVersion := flag.String("rustupVersion", "latest", "")
40+
rustupDownloadUrl := flag.String("rustupDownloadUrl", "", "")
41+
profile := flag.String("profile", "minimal", "")
42+
components := flag.String("components", "rustfmt,rust-analyzer,rust-src,clippy", "")
43+
enableWindowsTarget := flag.Bool("enableWindowsTarget", false, "")
44+
flag.Parse()
45+
46+
// TODO: does this even make sense for rustup? It needs internet access anyway to download toolchains, components, etc.
47+
installer.HandleOverride(rustupDownloadUrl, "https://static.rust-lang.org/rustup/archive", "rustup-download-url")
48+
49+
// Create and process the feature
50+
feature := installer.NewFeature("PF Rust", true,
51+
&rustupComponent{
52+
ComponentBase: installer.NewComponentBase("rustup", *rustupVersion),
53+
profile: *profile,
54+
rustupDownloadUrl: *rustupDownloadUrl,
55+
},
56+
&rustComponent{
57+
ComponentBase: installer.NewComponentBase("rust", *version),
58+
components: *components,
59+
profile: *profile,
60+
},
61+
&buildEssentialComponent{
62+
ComponentBase: installer.NewComponentBase("build-essential", installer.VERSION_SYSTEM_DEFAULT),
63+
},
64+
)
65+
// Optional component
66+
if *enableWindowsTarget {
67+
feature.AddComponents(&windowsTargetComponent{
68+
ComponentBase: installer.NewComponentBase("windows-target", installer.VERSION_IRRELEVANT),
69+
})
70+
}
71+
// Last component
72+
feature.AddComponents(&permissionsComponent{
73+
ComponentBase: installer.NewComponentBase("permissions", installer.VERSION_IRRELEVANT),
74+
})
75+
return feature.Process()
76+
}
77+
78+
//////////
79+
// Implementation
80+
//////////
81+
82+
type rustupComponent struct {
83+
*installer.ComponentBase
84+
profile string
85+
rustupDownloadUrl string
86+
}
87+
88+
func (c *rustupComponent) GetAllVersions() ([]*gover.Version, error) {
89+
allTags, err := installer.Tools.GitHub.GetTags("rust-lang", "rustup")
90+
if err != nil {
91+
return nil, err
92+
}
93+
return installer.Tools.Versioning.ParseVersionsFromList(allTags, threeDigitRegex, true)
94+
}
95+
96+
func (c *rustupComponent) InstallVersion(version *gover.Version) error {
97+
// Download the file
98+
archPart, err := installer.Tools.System.MapArchitecture(map[string]string{
99+
installer.AMD64: "x86_64",
100+
installer.ARM64: "aarch64",
101+
})
102+
if err != nil {
103+
return err
104+
}
105+
fileName := "rustup-init"
106+
downloadUrl := fmt.Sprintf("%s/%s/%s-unknown-linux-gnu/rustup-init", c.rustupDownloadUrl, version.Raw, archPart)
107+
if err := installer.Tools.Download.ToFile(downloadUrl, fileName, "Rustup-Init"); err != nil {
108+
return err
109+
}
110+
// Install it
111+
if err := os.Chmod(fileName, os.ModePerm); err != nil {
112+
return err
113+
}
114+
if err := execr.Run(true, "./"+fileName, "-y", "--default-toolchain", "none", "--no-modify-path", "--profile", c.profile); err != nil {
115+
return err
116+
}
117+
// Cleanup
118+
if err := os.Remove(fileName); err != nil {
119+
return err
120+
}
121+
return nil
122+
}
123+
124+
type rustComponent struct {
125+
*installer.ComponentBase
126+
profile string
127+
components string
128+
}
129+
130+
func (c *rustComponent) GetAllVersions() ([]*gover.Version, error) {
131+
allTags, err := installer.Tools.GitHub.GetTags("rust-lang", "rust")
132+
if err != nil {
133+
return nil, err
134+
}
135+
return installer.Tools.Versioning.ParseVersionsFromList(allTags, semVerRegex, true)
136+
}
137+
138+
func (c *rustComponent) InstallVersion(version *gover.Version) error {
139+
// Install it
140+
if err := execr.Run(true, "rustup", "toolchain", "install", "--profile", c.profile, "--no-self-update", version.Raw); err != nil {
141+
return err
142+
}
143+
// Installing the components
144+
fmt.Printf("Installing components: %s\n", c.components)
145+
args := []string{
146+
"component",
147+
"add",
148+
}
149+
for _, component := range strings.Split(c.components, ",") {
150+
trimmed := strings.TrimSpace(component)
151+
if trimmed != "" {
152+
args = append(args, trimmed)
153+
}
154+
}
155+
if err := execr.Run(true, "rustup", args...); err != nil {
156+
return err
157+
}
158+
return nil
159+
}
160+
161+
type buildEssentialComponent struct {
162+
*installer.ComponentBase
163+
}
164+
165+
func (c *buildEssentialComponent) InstallVersion(version *gover.Version) error {
166+
return installer.Tools.System.InstallPackages("build-essential")
167+
}
168+
169+
type windowsTargetComponent struct {
170+
*installer.ComponentBase
171+
}
172+
173+
func (c *windowsTargetComponent) InstallVersion(version *gover.Version) error {
174+
if err := execr.Run(true, "rustup", "target", "add", "x86_64-pc-windows-gnu"); err != nil {
175+
return err
176+
}
177+
if err := installer.Tools.System.InstallPackages("mingw-w64"); err != nil {
178+
return err
179+
}
180+
return nil
181+
}
182+
183+
type permissionsComponent struct {
184+
*installer.ComponentBase
185+
}
186+
187+
func (c *permissionsComponent) InstallVersion(version *gover.Version) error {
188+
return execr.Run(true, "chmod", "-R", "777", os.Getenv("RUSTUP_HOME"), os.Getenv("CARGO_HOME"))
189+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
set -e
3+
4+
[[ -f "$(dirname "$0")/../functions.sh" ]] && source "$(dirname "$0")/../functions.sh"
5+
[[ -f "$(dirname "$0")/functions.sh" ]] && source "$(dirname "$0")/functions.sh"
6+
7+
check_version "$(rustup --version 2>/dev/null)" "rustup 1.27.1 (54dd3d00f 2024-04-24)"
8+
check_version "$(rustc --version)" "rustc 1.76.0 (07dca489a 2024-02-04)"
9+
check_version "$(rustup target list --installed)" $'x86_64-pc-windows-gnu\nx86_64-unknown-linux-gnu'
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
set -e
3+
4+
[[ -f "$(dirname "$0")/../functions.sh" ]] && source "$(dirname "$0")/../functions.sh"
5+
[[ -f "$(dirname "$0")/functions.sh" ]] && source "$(dirname "$0")/functions.sh"
6+
7+
check_version "$(rustup --version 2>/dev/null)" "rustup 1.27.1 (54dd3d00f 2024-04-24)"
8+
check_version "$(rustc --version)" "rustc 1.76.0 (07dca489a 2024-02-04)"
9+
check_version "$(rustup target list --installed)" $'x86_64-unknown-linux-gnu'

0 commit comments

Comments
 (0)