Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/pr_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,27 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- name: "Install WASI SDK (Ubuntu)"
if: matrix.platform == 'ubuntu-latest'
run: |
wget -q https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz
tar xzf wasi-sdk-25.0-x86_64-linux.tar.gz
echo "$(pwd)/wasi-sdk-25.0-x86_64-linux/bin" >> $GITHUB_PATH
shell: bash
- name: "Install WASI SDK (macOS)"
if: matrix.platform == 'macos-latest'
run: |
wget -q https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-arm64-macos.tar.gz
tar xzf wasi-sdk-25.0-arm64-macos.tar.gz
echo "$(pwd)/wasi-sdk-25.0-arm64-macos/bin" >> $GITHUB_PATH
shell: bash
- name: "Install WASI SDK (Windows)"
if: matrix.platform == 'windows-latest'
run: |
Invoke-WebRequest -Uri "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-windows.tar.gz" -OutFile "wasi-sdk.tar.gz"
tar -xzf wasi-sdk.tar.gz
echo "$PWD/wasi-sdk-25.0-x86_64-windows/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
shell: pwsh
- name: "Config Artifact"
uses: actions/download-artifact@v8
with:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

### Enhancements:

- feat(compute): add support for cpp for compute ([#1773](https://github.com/fastly/cli/pull/1773))

### Dependencies:
- build(deps): `golang.org/x/term` from 0.41.0 to 0.42.0 ([#1726](https://github.com/fastly/cli/pull/1726))
- build(deps): `golang.org/x/crypto` from 0.49.0 to 0.50.0 ([#1726](https://github.com/fastly/cli/pull/1726))
Expand Down
Binary file added fastly-dev
Binary file not shown.
6 changes: 6 additions & 0 deletions pkg/commands/compute/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,12 @@ func identifyToolchain(c *BuildCommand) (string, error) {
func language(toolchain, manifestFilename string, c *BuildCommand, in io.Reader, out io.Writer, spinner text.Spinner) (*Language, error) {
var language *Language
switch toolchain {
case "cpp":
language = NewLanguage(&LanguageOptions{
Name: "cpp",
SourceDirectory: CPPSourceDirectory,
Toolchain: NewCPP(c, in, manifestFilename, out, spinner),
})
case "go":
language = NewLanguage(&LanguageOptions{
Name: "go",
Expand Down
203 changes: 203 additions & 0 deletions pkg/commands/compute/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,209 @@ func TestBuildJavaScript(t *testing.T) {
}
}

func TestBuildCPP(t *testing.T) {
if os.Getenv("TEST_COMPUTE_BUILD_CPP") == "" && os.Getenv("TEST_COMPUTE_BUILD") == "" {
t.Log("skipping test")
t.Skip("Set TEST_COMPUTE_BUILD to run this test")
}

args := testutil.SplitArgs

scenarios := []struct {
name string
args []string
applicationConfig *config.File
fastlyManifest string
wantError string
wantRemediationError string
wantOutput []string
}{
{
name: "no fastly.toml manifest",
args: args("compute build"),
wantError: "error reading fastly.toml: file not found",
wantRemediationError: "Run `fastly compute init` to ensure a correctly configured manifest.",
},
{
name: "empty language",
args: args("compute build"),
fastlyManifest: `
manifest_version = 2
name = "test"`,
wantError: "language cannot be empty, please provide a language",
},
{
name: "unknown language",
args: args("compute build"),
fastlyManifest: `
manifest_version = 2
name = "test"
language = "foobar"`,
wantError: "unsupported language foobar",
},
// The following test validates that the project compiles successfully even
// though the fastly.toml manifest has no build script. There should be a
// default build script inserted.
//
// NOTE: This test passes --verbose so we can validate specific outputs.
{
name: "build script inserted dynamically when missing",
args: args("compute build --verbose"),
applicationConfig: &config.File{
Language: config.Language{
CPP: config.CPP{
ToolchainConstraint: ">= 14.0.0",
WasmWasiTarget: "wasm32-wasip1",
},
},
},
fastlyManifest: `
manifest_version = 2
name = "test"
language = "cpp"`,
wantOutput: []string{
"No [scripts.build] found in fastly.toml.", // requires --verbose
"The following default build command for C++ will be used",
"clang++ -O3 --target=wasm32-wasip1 -o ./bin/main.wasm main.cpp",
},
},
{
name: "build error",
args: args("compute build"),
applicationConfig: &config.File{
Language: config.Language{
CPP: config.CPP{
ToolchainConstraint: ">= 14.0.0",
WasmWasiTarget: "wasm32-wasip1",
},
},
},
fastlyManifest: `
manifest_version = 2
name = "test"
language = "cpp"

[scripts]
build = "echo no compilation happening"`,
wantRemediationError: compute.DefaultBuildErrorRemediation,
},
// NOTE: This test passes --verbose so we can validate specific outputs.
{
name: "successful build",
args: args("compute build --verbose"),
applicationConfig: &config.File{
Language: config.Language{
CPP: config.CPP{
ToolchainConstraint: ">= 14.0.0",
WasmWasiTarget: "wasm32-wasip1",
},
},
},
fastlyManifest: `
manifest_version = 2
name = "test"
language = "cpp"

[scripts]
build = "clang++ -O3 --target=wasm32-wasip1 -o bin/main.wasm main.cpp"`,
wantOutput: []string{
"Creating ./bin directory (for Wasm binary)",
"Built package",
},
},
}
for testcaseIdx := range scenarios {
testcase := &scenarios[testcaseIdx]
t.Run(testcase.name, func(t *testing.T) {
// We're going to chdir to a build environment,
// so save the PWD to return to, afterwards.
pwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}

wasmtoolsBinName := "wasm-tools"

// Windows was having issues when trying to move a tmpBin file (which
// represents the latest binary downloaded from GitHub) to binPath (which
// represents the existing binary installed on a user's machine).
//
// The problem was, for the sake of the tests, I just create one file
// `wasmtoolsBinName` and used that for both `tmpBin` and `binPath` and
// this works fine on *nix systems. But once Windows did `os.Rename()` and
// move tmpBin to binPath it would no longer be able to set permissions on
// the binPath because it didn't think the file existed any more. My guess
// is that moving a file over itself causes Windows to remove the file.
//
// So to work around that issue I just create two separate files because
// in reality that's what the CLI will be dealing with. I only used one
// file for the sake of test case convenience (which ironically became
// very INCONVENIENT when the tests started unexpectedly failing on
// Windows and caused me a long time debugging).
latestDownloaded := wasmtoolsBinName + "-latest-downloaded"

// Create test environment
rootdir := testutil.NewEnv(testutil.EnvOpts{
T: t,
Copy: []testutil.FileIO{
{Src: filepath.Join("testdata", "build", "cpp", "main.cpp"), Dst: "main.cpp"},
},
Write: []testutil.FileIO{
{Src: `#!/usr/bin/env bash
echo wasm-tools 1.0.4`, Dst: wasmtoolsBinName, Executable: true},
{Src: `#!/usr/bin/env bash
echo wasm-tools 2.0.0`, Dst: latestDownloaded, Executable: true},
{Src: testcase.fastlyManifest, Dst: manifest.Filename},
},
})
defer os.RemoveAll(rootdir)
wasmtoolsBinPath := filepath.Join(rootdir, wasmtoolsBinName)

// Before running the test, chdir into the build environment.
// When we're done, chdir back to our original location.
// This is so we can reliably copy the testdata/ fixtures.
if err := os.Chdir(rootdir); err != nil {
t.Fatal(err)
}
defer func() {
_ = os.Chdir(pwd)
}()

var stdout threadsafe.Buffer
app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
opts := testutil.MockGlobalData(testcase.args, &stdout)
if testcase.applicationConfig != nil {
opts.Config = *testcase.applicationConfig
}
opts.Versioners = global.Versioners{
WasmTools: mock.AssetVersioner{
AssetVersion: "1.2.3",
BinaryFilename: wasmtoolsBinName,
DownloadOK: true,
DownloadedFile: latestDownloaded,
InstallFilePath: wasmtoolsBinPath, // avoid overwriting developer's actual wasm-tools install
},
}
return opts, nil
}
err = app.Run(testcase.args, nil)

t.Log(stdout.String())

testutil.AssertRemediationErrorContains(t, err, testcase.wantRemediationError)

// NOTE: Some errors we want to assert only the remediation.
// e.g. a 'stat' error isn't the same across operating systems/platforms.
if testcase.wantError != "" {
testutil.AssertErrorContains(t, err, testcase.wantError)
}
for _, s := range testcase.wantOutput {
testutil.AssertStringContains(t, stdout.String(), s)
}
})
}
}

// NOTE: TestBuildOther also validates the post_build settings.
func TestBuildOther(t *testing.T) {
args := testutil.SplitArgs
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/compute/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ type InitCommand struct {
}

// Languages is a list of supported language options.
var Languages = []string{"rust", "javascript", "go", "other"}
var Languages = []string{"rust", "javascript", "go", "cpp", "other"}

// NewInitCommand returns a usable command registered under the parent.
func NewInitCommand(parent argparser.Registerer, g *global.Data) *InitCommand {
Expand Down
5 changes: 5 additions & 0 deletions pkg/commands/compute/language.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func NewLanguages(kits config.StarterKitLanguages) []*Language {
DisplayName: "Go",
StarterKits: kits.Go,
}),
NewLanguage(&LanguageOptions{
Name: "cpp",
DisplayName: "C++",
StarterKits: kits.CPP,
}),
NewLanguage(&LanguageOptions{
Name: "other",
DisplayName: "Other ('bring your own' Wasm binary)",
Expand Down
Loading
Loading