Skip to content

Commit 0e1f5e1

Browse files
feat(compute): add support for cpp for compute
1 parent a917e46 commit 0e1f5e1

8 files changed

Lines changed: 428 additions & 1 deletion

File tree

fastly-dev

35.3 MB
Binary file not shown.

pkg/commands/compute/build.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,12 @@ func identifyToolchain(c *BuildCommand) (string, error) {
696696
func language(toolchain, manifestFilename string, c *BuildCommand, in io.Reader, out io.Writer, spinner text.Spinner) (*Language, error) {
697697
var language *Language
698698
switch toolchain {
699+
case "cpp":
700+
language = NewLanguage(&LanguageOptions{
701+
Name: "cpp",
702+
SourceDirectory: CPPSourceDirectory,
703+
Toolchain: NewCPP(c, in, manifestFilename, out, spinner),
704+
})
699705
case "go":
700706
language = NewLanguage(&LanguageOptions{
701707
Name: "go",

pkg/commands/compute/build_test.go

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,209 @@ func TestBuildJavaScript(t *testing.T) {
662662
}
663663
}
664664

665+
func TestBuildCPP(t *testing.T) {
666+
if os.Getenv("TEST_COMPUTE_BUILD_CPP") == "" && os.Getenv("TEST_COMPUTE_BUILD") == "" {
667+
t.Log("skipping test")
668+
t.Skip("Set TEST_COMPUTE_BUILD to run this test")
669+
}
670+
671+
args := testutil.SplitArgs
672+
673+
scenarios := []struct {
674+
name string
675+
args []string
676+
applicationConfig *config.File
677+
fastlyManifest string
678+
wantError string
679+
wantRemediationError string
680+
wantOutput []string
681+
}{
682+
{
683+
name: "no fastly.toml manifest",
684+
args: args("compute build"),
685+
wantError: "error reading fastly.toml: file not found",
686+
wantRemediationError: "Run `fastly compute init` to ensure a correctly configured manifest.",
687+
},
688+
{
689+
name: "empty language",
690+
args: args("compute build"),
691+
fastlyManifest: `
692+
manifest_version = 2
693+
name = "test"`,
694+
wantError: "language cannot be empty, please provide a language",
695+
},
696+
{
697+
name: "unknown language",
698+
args: args("compute build"),
699+
fastlyManifest: `
700+
manifest_version = 2
701+
name = "test"
702+
language = "foobar"`,
703+
wantError: "unsupported language foobar",
704+
},
705+
// The following test validates that the project compiles successfully even
706+
// though the fastly.toml manifest has no build script. There should be a
707+
// default build script inserted.
708+
//
709+
// NOTE: This test passes --verbose so we can validate specific outputs.
710+
{
711+
name: "build script inserted dynamically when missing",
712+
args: args("compute build --verbose"),
713+
applicationConfig: &config.File{
714+
Language: config.Language{
715+
CPP: config.CPP{
716+
ToolchainConstraint: ">= 14.0.0",
717+
WasmWasiTarget: "wasm32-wasip1",
718+
},
719+
},
720+
},
721+
fastlyManifest: `
722+
manifest_version = 2
723+
name = "test"
724+
language = "cpp"`,
725+
wantOutput: []string{
726+
"No [scripts.build] found in fastly.toml.", // requires --verbose
727+
"The following default build command for C++ will be used",
728+
"clang++ -O3 --target=wasm32-wasip1 -o ./bin/main.wasm main.cpp",
729+
},
730+
},
731+
{
732+
name: "build error",
733+
args: args("compute build"),
734+
applicationConfig: &config.File{
735+
Language: config.Language{
736+
CPP: config.CPP{
737+
ToolchainConstraint: ">= 14.0.0",
738+
WasmWasiTarget: "wasm32-wasip1",
739+
},
740+
},
741+
},
742+
fastlyManifest: `
743+
manifest_version = 2
744+
name = "test"
745+
language = "cpp"
746+
747+
[scripts]
748+
build = "echo no compilation happening"`,
749+
wantRemediationError: compute.DefaultBuildErrorRemediation,
750+
},
751+
// NOTE: This test passes --verbose so we can validate specific outputs.
752+
{
753+
name: "successful build",
754+
args: args("compute build --verbose"),
755+
applicationConfig: &config.File{
756+
Language: config.Language{
757+
CPP: config.CPP{
758+
ToolchainConstraint: ">= 14.0.0",
759+
WasmWasiTarget: "wasm32-wasip1",
760+
},
761+
},
762+
},
763+
fastlyManifest: `
764+
manifest_version = 2
765+
name = "test"
766+
language = "cpp"
767+
768+
[scripts]
769+
build = "clang++ -O3 --target=wasm32-wasip1 -o bin/main.wasm main.cpp"`,
770+
wantOutput: []string{
771+
"Creating ./bin directory (for Wasm binary)",
772+
"Built package",
773+
},
774+
},
775+
}
776+
for testcaseIdx := range scenarios {
777+
testcase := &scenarios[testcaseIdx]
778+
t.Run(testcase.name, func(t *testing.T) {
779+
// We're going to chdir to a build environment,
780+
// so save the PWD to return to, afterwards.
781+
pwd, err := os.Getwd()
782+
if err != nil {
783+
t.Fatal(err)
784+
}
785+
786+
wasmtoolsBinName := "wasm-tools"
787+
788+
// Windows was having issues when trying to move a tmpBin file (which
789+
// represents the latest binary downloaded from GitHub) to binPath (which
790+
// represents the existing binary installed on a user's machine).
791+
//
792+
// The problem was, for the sake of the tests, I just create one file
793+
// `wasmtoolsBinName` and used that for both `tmpBin` and `binPath` and
794+
// this works fine on *nix systems. But once Windows did `os.Rename()` and
795+
// move tmpBin to binPath it would no longer be able to set permissions on
796+
// the binPath because it didn't think the file existed any more. My guess
797+
// is that moving a file over itself causes Windows to remove the file.
798+
//
799+
// So to work around that issue I just create two separate files because
800+
// in reality that's what the CLI will be dealing with. I only used one
801+
// file for the sake of test case convenience (which ironically became
802+
// very INCONVENIENT when the tests started unexpectedly failing on
803+
// Windows and caused me a long time debugging).
804+
latestDownloaded := wasmtoolsBinName + "-latest-downloaded"
805+
806+
// Create test environment
807+
rootdir := testutil.NewEnv(testutil.EnvOpts{
808+
T: t,
809+
Copy: []testutil.FileIO{
810+
{Src: filepath.Join("testdata", "build", "cpp", "main.cpp"), Dst: "main.cpp"},
811+
},
812+
Write: []testutil.FileIO{
813+
{Src: `#!/usr/bin/env bash
814+
echo wasm-tools 1.0.4`, Dst: wasmtoolsBinName, Executable: true},
815+
{Src: `#!/usr/bin/env bash
816+
echo wasm-tools 2.0.0`, Dst: latestDownloaded, Executable: true},
817+
{Src: testcase.fastlyManifest, Dst: manifest.Filename},
818+
},
819+
})
820+
defer os.RemoveAll(rootdir)
821+
wasmtoolsBinPath := filepath.Join(rootdir, wasmtoolsBinName)
822+
823+
// Before running the test, chdir into the build environment.
824+
// When we're done, chdir back to our original location.
825+
// This is so we can reliably copy the testdata/ fixtures.
826+
if err := os.Chdir(rootdir); err != nil {
827+
t.Fatal(err)
828+
}
829+
defer func() {
830+
_ = os.Chdir(pwd)
831+
}()
832+
833+
var stdout threadsafe.Buffer
834+
app.Init = func(_ []string, _ io.Reader) (*global.Data, error) {
835+
opts := testutil.MockGlobalData(testcase.args, &stdout)
836+
if testcase.applicationConfig != nil {
837+
opts.Config = *testcase.applicationConfig
838+
}
839+
opts.Versioners = global.Versioners{
840+
WasmTools: mock.AssetVersioner{
841+
AssetVersion: "1.2.3",
842+
BinaryFilename: wasmtoolsBinName,
843+
DownloadOK: true,
844+
DownloadedFile: latestDownloaded,
845+
InstallFilePath: wasmtoolsBinPath, // avoid overwriting developer's actual wasm-tools install
846+
},
847+
}
848+
return opts, nil
849+
}
850+
err = app.Run(testcase.args, nil)
851+
852+
t.Log(stdout.String())
853+
854+
testutil.AssertRemediationErrorContains(t, err, testcase.wantRemediationError)
855+
856+
// NOTE: Some errors we want to assert only the remediation.
857+
// e.g. a 'stat' error isn't the same across operating systems/platforms.
858+
if testcase.wantError != "" {
859+
testutil.AssertErrorContains(t, err, testcase.wantError)
860+
}
861+
for _, s := range testcase.wantOutput {
862+
testutil.AssertStringContains(t, stdout.String(), s)
863+
}
864+
})
865+
}
866+
}
867+
665868
// NOTE: TestBuildOther also validates the post_build settings.
666869
func TestBuildOther(t *testing.T) {
667870
args := testutil.SplitArgs

pkg/commands/compute/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ type InitCommand struct {
5757
}
5858

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

6262
// NewInitCommand returns a usable command registered under the parent.
6363
func NewInitCommand(parent argparser.Registerer, g *global.Data) *InitCommand {

pkg/commands/compute/language.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ func NewLanguages(kits config.StarterKitLanguages) []*Language {
3737
DisplayName: "Go",
3838
StarterKits: kits.Go,
3939
}),
40+
NewLanguage(&LanguageOptions{
41+
Name: "cpp",
42+
DisplayName: "C++",
43+
StarterKits: kits.CPP,
44+
}),
4045
NewLanguage(&LanguageOptions{
4146
Name: "other",
4247
DisplayName: "Other ('bring your own' Wasm binary)",

0 commit comments

Comments
 (0)