Skip to content

Commit e732f14

Browse files
authored
[Beam] Fable Beam (alpha) (#4340)
1 parent f6f4b98 commit e732f14

127 files changed

Lines changed: 34991 additions & 4 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.devcontainer/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s - -y
9090
RUN echo "# Load rust path" >> .zshrc
9191
RUN echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> .zshrc
9292

93+
# Install Erlang/OTP (for Beam target)
94+
# Debian Trixie includes OTP 27+ in default repos
95+
RUN sudo apt-get update && sudo apt-get install -y erlang
96+
9397
# Instal dart
9498
# Use custom dsm install script, because we broke it while
9599
# trying to fix another issue with dsm

.github/workflows/build.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,26 @@ jobs:
221221
- name: Fable Tests - Dart
222222
run: ./build.sh test dart
223223

224+
# Separate build job for Beam (Erlang)
225+
build-beam:
226+
runs-on: ubuntu-latest
227+
228+
steps:
229+
- uses: actions/checkout@v5
230+
231+
- name: Setup .NET
232+
uses: actions/setup-dotnet@v5
233+
with:
234+
dotnet-version: "10.0.x"
235+
236+
- name: Setup Erlang/OTP
237+
uses: erlef/setup-beam@v1
238+
with:
239+
otp-version: "27"
240+
241+
- name: Fable Tests - Beam
242+
run: ./build.sh test beam
243+
224244
# Separate job to run F# analyzers
225245
analyzers:
226246
runs-on: ubuntu-latest

src/Fable.AST/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Added
11+
12+
* [Beam] Add `Language.Beam` case and Erlang AST types (`Beam.AST.fs`) (by @dbrattli)
13+
1014
## 5.0.0-beta.3 - 2025-11-25
1115

1216
* [All] Fix #4221 Fable.Type union case arg names (by @ncave)

src/Fable.AST/Plugins.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type Language =
1616
| Php
1717
| Dart
1818
| Rust
19+
| Beam
1920

2021
override this.ToString() =
2122
match this with
@@ -25,6 +26,7 @@ type Language =
2526
| Php -> "PHP"
2627
| Dart -> "Dart"
2728
| Rust -> "Rust"
29+
| Beam -> "Beam"
2830

2931
type CompilerOptions =
3032
{

src/Fable.Build/Fable.Build.fsproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<Compile Include="FableLibrary/TypeScript.fs" />
1616
<Compile Include="FableLibrary/JavaScript.fs" />
1717
<Compile Include="FableLibrary/Rust.fs" />
18+
<Compile Include="FableLibrary/Beam.fs" />
1819
<Compile Include="WorkerJs.fs" />
1920
<Compile Include="Standalone.fs" />
2021
<Compile Include="CompilerJs.fs" />
@@ -24,6 +25,7 @@
2425
<Compile Include="Test/Rust.fs" />
2526
<Compile Include="Test/Dart.fs" />
2627
<Compile Include="Test/TypeScript.fs" />
28+
<Compile Include="Test/Beam.fs" />
2729
<Compile Include="Test/Integration.fs" />
2830
<Compile Include="Test/CompilerJs.fs" />
2931
<Compile Include="Quicktest/Core.fs" />
@@ -32,6 +34,7 @@
3234
<Compile Include="Quicktest/Python.fs" />
3335
<Compile Include="Quicktest/Rust.fs" />
3436
<Compile Include="Quicktest/Dart.fs" />
37+
<Compile Include="Quicktest/Beam.fs" />
3538
<Compile Include="FcsRepo.fs" />
3639
<Compile Include="Publish.fs" />
3740
<Compile Include="GithubRelease.fs" />
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Build.FableLibrary
2+
3+
open System.IO
4+
5+
type BuildFableLibraryBeam() =
6+
inherit
7+
BuildFableLibrary(
8+
"beam",
9+
Path.Combine("src", "fable-library-beam"),
10+
Path.Combine("src", "fable-library-beam"),
11+
Path.Combine("temp", "fable-library-beam"),
12+
Path.Combine("temp", "fable-library-beam")
13+
)
14+
15+
override this.CopyStage() =
16+
// Copy hand-written .erl runtime files to the output directory
17+
for erlFile in Directory.GetFiles(this.SourceDir, "*.erl") do
18+
File.Copy(erlFile, Path.Combine(this.OutDir, Path.GetFileName(erlFile)), overwrite = true)

src/Fable.Build/Main.fs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Available commands:
2929
python Run for Python
3030
dart Run for Dart
3131
rust Run for Rust
32+
beam Run for BEAM (Erlang)
3233
3334
Options:
3435
--skip-fable-library Skip building fable-library if folder already exists
@@ -40,6 +41,7 @@ Available commands:
4041
python Run the tests for Python
4142
dart Run the tests for Dart
4243
rust Run the tests for Rust
44+
beam Run the tests for BEAM (Erlang)
4345
integration Run the integration test suite
4446
standalone Tests the standalone version of Fable
4547
(Fable running on top of Node.js)
@@ -126,6 +128,7 @@ let main argv =
126128
| "--python" :: _ -> BuildFableLibraryPython().Run()
127129
| "--dart" :: _ -> BuildFableLibraryDart().Run()
128130
| "--rust" :: _ -> BuildFableLibraryRust().Run()
131+
| "--beam" :: _ -> BuildFableLibraryBeam().Run()
129132
| _ -> printHelp ()
130133
| "test" :: args ->
131134
match args with
@@ -134,6 +137,7 @@ let main argv =
134137
| "python" :: args -> Test.Python.handle args
135138
| "dart" :: args -> Test.Dart.handle args
136139
| "rust" :: args -> Test.Rust.handle args
140+
| "beam" :: args -> Test.Beam.handle args
137141
| "integration" :: args -> Test.Integration.handle args
138142
| "standalone" :: _ -> Test.Standalone.handle args
139143
// This test is using quicktest project for now,
@@ -147,6 +151,7 @@ let main argv =
147151
| "python" :: _ -> Quicktest.Python.handle args
148152
| "dart" :: _ -> Quicktest.Dart.handle args
149153
| "rust" :: _ -> Quicktest.Rust.handle args
154+
| "beam" :: _ -> Quicktest.Beam.handle args
150155
| _ -> printHelp ()
151156
| "standalone" :: args -> Standalone.handle args
152157
| "compiler-js" :: args -> CompilerJs.handle args

src/Fable.Build/Quicktest/Beam.fs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module Build.Quicktest.Beam
2+
3+
open Build.FableLibrary
4+
open Build.Quicktest.Core
5+
6+
let handle (args: string list) =
7+
genericQuicktest
8+
{
9+
Language = "beam"
10+
FableLibBuilder = BuildFableLibraryBeam()
11+
ProjectDir = "src/quicktest-beam"
12+
Extension = ".erl"
13+
RunMode = RunScript
14+
}
15+
args

src/Fable.Build/Test/Beam.fs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
module Build.Test.Beam
2+
3+
open Build.FableLibrary
4+
open System.IO
5+
open Build.Utils
6+
open BlackFox.CommandLine
7+
open SimpleExec
8+
9+
let private buildDir = Path.Resolve("temp", "tests", "Beam")
10+
let private sourceDir = Path.Resolve("tests", "Beam")
11+
let private testRunnerSrc = Path.Resolve("tests", "Beam", "erl_test_runner.erl")
12+
13+
let handle (args: string list) =
14+
let isWatch = args |> List.contains "--watch"
15+
let noDotnet = args |> List.contains "--no-dotnet"
16+
let skipFableLibrary = args |> List.contains "--skip-fable-library"
17+
18+
BuildFableLibraryBeam().Run(skipFableLibrary)
19+
20+
Directory.clean buildDir
21+
22+
let fableArgs =
23+
CmdLine.concat
24+
[
25+
CmdLine.empty
26+
|> CmdLine.appendRaw sourceDir
27+
|> CmdLine.appendPrefix "--outDir" buildDir
28+
|> CmdLine.appendPrefix "--lang" "beam"
29+
|> CmdLine.appendPrefix "--exclude" "Fable.Core"
30+
|> CmdLine.appendRaw "--noCache"
31+
32+
if isWatch then
33+
CmdLine.empty |> CmdLine.appendRaw "--watch"
34+
]
35+
36+
if isWatch then
37+
Async.Parallel
38+
[
39+
if not noDotnet then
40+
Command.RunAsync("dotnet", "watch test -c Release", workingDirectory = sourceDir)
41+
|> Async.AwaitTask
42+
43+
Command.WatchFableAsync(fableArgs, workingDirectory = buildDir)
44+
|> Async.AwaitTask
45+
]
46+
|> Async.RunSynchronously
47+
|> ignore
48+
else
49+
// Test against .NET
50+
Command.Run("dotnet", "test -c Release", workingDirectory = sourceDir)
51+
52+
// Compile tests to Erlang
53+
Command.Fable(fableArgs, workingDirectory = buildDir)
54+
55+
// Copy test runner to build dir
56+
File.Copy(testRunnerSrc, Path.Combine(buildDir, "erl_test_runner.erl"), overwrite = true)
57+
58+
// Copy native Erlang test modules to build dir
59+
let nativeErlDir = Path.Resolve("tests", "Beam", "erl")
60+
61+
if Directory.Exists(nativeErlDir) then
62+
for erlFile in Directory.GetFiles(nativeErlDir, "*.erl") do
63+
let fileName = Path.GetFileName(erlFile)
64+
File.Copy(erlFile, Path.Combine(buildDir, fileName), overwrite = true)
65+
66+
// fable-library-beam files are auto-copied to fable_modules/fable-library-beam/ by the compiler
67+
let fableModulesLibDir =
68+
Path.Combine(buildDir, "fable_modules", "fable-library-beam")
69+
70+
// Compile fable-library-beam .erl files first
71+
let mutable compileErrors = 0
72+
73+
if Directory.Exists(fableModulesLibDir) then
74+
for erlFile in Directory.GetFiles(fableModulesLibDir, "*.erl") do
75+
let fileName = Path.GetFileName(erlFile)
76+
77+
try
78+
Command.Run("erlc", fileName, workingDirectory = fableModulesLibDir)
79+
with _ ->
80+
printfn $"WARNING: erlc failed for {fileName} (library), skipping"
81+
compileErrors <- compileErrors + 1
82+
83+
// Compile test .erl files with library on code path
84+
let erlFiles = Directory.GetFiles(buildDir, "*.erl")
85+
86+
for erlFile in erlFiles do
87+
let fileName = Path.GetFileName(erlFile)
88+
89+
try
90+
Command.Run("erlc", $"-pa fable_modules/fable-library-beam {fileName}", workingDirectory = buildDir)
91+
with _ ->
92+
printfn $"WARNING: erlc failed for {fileName}, skipping"
93+
compileErrors <- compileErrors + 1
94+
95+
if compileErrors > 0 then
96+
printfn $"WARNING: {compileErrors} file(s) failed to compile with erlc"
97+
98+
// Run Erlang test runner with library on code path
99+
Command.Run(
100+
"erl",
101+
"-noshell -pa . -pa fable_modules/fable-library-beam -eval \"erl_test_runner:main([\\\".\\\"])\" -s init stop",
102+
workingDirectory = buildDir
103+
)

src/Fable.Cli/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Added
11+
12+
* [Beam] Add Erlang/BEAM target (`--lang beam`). Compiles F# to `.erl` source files. 2010 tests passing. (by @dbrattli)
13+
1014
## 5.0.0-alpha.24 - 2026-02-13
1115

1216
### Fixed

0 commit comments

Comments
 (0)