Skip to content

Commit 27382b7

Browse files
authored
chore: implement incremental build for FableLibrary tasks (#4438)
1 parent 966ad3e commit 27382b7

25 files changed

Lines changed: 223 additions & 103 deletions

.github/workflows/build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,11 @@ jobs:
175175

176176
- name: Fable Tests - Python (linux)
177177
if: matrix.platform == 'ubuntu-latest'
178-
run: ./build.sh test python --skip-fable-library
178+
run: ./build.sh test python
179179

180180
- name: Fable Tests - Python (Windows)
181181
if: matrix.platform == 'windows-latest'
182-
run: .\build.bat test python --skip-fable-library
182+
run: .\build.bat test python
183183

184184
# Separate build job for Rust (will run in parallel)
185185
build-rust:
@@ -209,7 +209,7 @@ jobs:
209209
run: ./build.sh fable-library --rust
210210

211211
- name: Fable Tests - Rust
212-
run: ./build.sh test rust --skip-fable-library --${{ matrix.test }}
212+
run: ./build.sh test rust --${{ matrix.test }}
213213

214214
# Separate build job for Dart
215215
build-dart:

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,5 @@ Cargo.lock
232232

233233
# This file is copied as part of the Restore task
234234
tests/React/Components.Copied.fs
235+
236+
.build-cache/

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ The build system is implemented in F# at `src/Fable.Build/`. All commands go thr
1919
# Run tests (targets: javascript, typescript, python, dart, rust, beam, integration)
2020
./build.sh test javascript
2121
./build.sh test javascript --watch # Watch mode (recompile on changes)
22-
./build.sh test javascript --skip-fable-library # Skip fable-library rebuild (only if fable-library source is unchanged)
22+
./build.sh test javascript --force-fable-library # Force fable-library rebuild (needed if you modified Fable compiler to fix the generation of `src/fable-library-*/**/*.fs` files)
2323

2424
# Python-specific options
2525
./build.sh test python --skip-fable-library-core # Skip slow Rust extension + .pyi rebuild (Python only)
@@ -29,7 +29,7 @@ The build system is implemented in F# at `src/Fable.Build/`. All commands go thr
2929
./build.sh quicktest javascript # Also: typescript, python, dart, rust, beam
3030
```
3131

32-
`--skip-fable-library` is safe when changes are only in `src/Fable.Transforms/` or other compiler code. If you modified runtime library source (e.g., `src/fable-library-py/`, `src/fable-library-ts/`), do not skip. If you already ran `./build.sh fable-library` separately, use `--skip-fable-library` when running tests right afterwards to avoid rebuilding.
32+
In most cases, you should not need to use `--force-fable-library`. Only use it if you modified compiler code that affects how F# files used in `src/fable-library-*/` are generated (e.g., changes to `FSharp2Fable.fs` that affect how F# AST is transformed into Fable AST for library files).
3333

3434
Build output goes to `temp/`: transpiled runtime libraries in `temp/fable-library-<target>/` and test output in `temp/tests/<target>/` (e.g., `temp/fable-library-beam/` and `temp/tests/beam/`).
3535

src/Fable.Build/Fable.Build.fsproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
</PropertyGroup>
77
<ItemGroup>
88
<Compile Include="Utils.fs" />
9-
<Compile Include="Utils/LastVersionFinder.fs" />
109
<Compile Include="Workspace.fs" />
10+
<Compile Include="Utils/LastVersionFinder.fs" />
11+
<Compile Include="Utils/IncrementalBuild.fs" />
1112
<Compile Include="SimpleExec.Extensions.fs" />
1213
<Compile Include="FableLibrary/Core.fs" />
1314
<Compile Include="FableLibrary/Python.fs" />
@@ -46,6 +47,7 @@
4647
<PackageReference Include="EasyBuild.Tools" Version="6.0.0" />
4748
<PackageReference Include="Fake.IO.FileSystem" Version="6.1.4" />
4849
<PackageReference Include="FsToolkit.ErrorHandling" Version="5.2.0" />
50+
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="10.0.5" />
4951
<PackageReference Include="SimpleExec" Version="13.0.0" />
5052
<PackageReference Include="Thoth.Json.Net" Version="12.0.0" />
5153
<PackageReference Include="Semver" Version="3.0.0" />

src/Fable.Build/FableLibrary/Beam.fs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ type BuildFableLibraryBeam() =
99
Path.Combine("src", "fable-library-beam"),
1010
Path.Combine("src", "fable-library-beam"),
1111
Path.Combine("temp", "fable-library-beam"),
12-
Path.Combine("temp", "fable-library-beam")
12+
Path.Combine("temp", "fable-library-beam"),
13+
[
14+
Path.Combine("src", "fable-library-beam", "**", "*.erl")
15+
Path.Combine("src", "fable-library-beam", "**", "*.fs")
16+
Path.Combine("src", "fable-library-ts", "**", "*.fs")
17+
]
1318
)
1419

1520
override this.CopyStage() =

src/Fable.Build/FableLibrary/Core.fs

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
namespace Build.FableLibrary
22

33
open BlackFox.CommandLine
4-
open Fake.IO
54
open System.IO
65
open Build.Utils
7-
open Build.Utils
8-
open System.Diagnostics
96
open SimpleExec
7+
open Microsoft.Extensions.FileSystemGlobbing
8+
open Microsoft.Extensions.FileSystemGlobbing.Abstractions
109

1110
/// <summary>
1211
/// Building fable-library is similar enough for all the targets
1312
/// that we can use this class to standardise the process.
1413
/// </summary>
1514
type BuildFableLibrary
16-
(language: string, libraryDir: string, sourceDir: string, buildDir: string, outDir: string, ?fableLibArg: string)
15+
(
16+
language: string,
17+
libraryDir: string,
18+
sourceDir: string,
19+
buildDir: string,
20+
outDir: string,
21+
inputPatterns: string list,
22+
?fableLibArg: string
23+
)
1724
=
1825

1926
// It seems like the different target have a different way of supporting
@@ -62,37 +69,50 @@ type BuildFableLibrary
6269

6370
tryDelete 3
6471

65-
member this.Run(?skipIfExist: bool) =
72+
member this.Run(?forceBuild: bool) =
6673
let toConsole (s: string) = System.Console.WriteLine(s)
6774

68-
let skipIfExist = defaultArg skipIfExist false
69-
70-
if skipIfExist && Directory.Exists outDir then
71-
"Skipping Fable build stage" |> toConsole
72-
73-
else
74-
75-
"Cleaning build directory" |> toConsole
76-
77-
this.deleteDirectoryRobust buildDir
78-
79-
"Building Fable.Library" |> toConsole
80-
81-
let args =
82-
CmdLine.appendRaw sourceDir
83-
>> CmdLine.appendPrefix "--outDir" outDir
84-
>> CmdLine.appendPrefix "--fableLib" fableLibArg
85-
>> CmdLine.appendPrefix "--lang" language
86-
>> CmdLine.appendPrefix "--exclude" "Fable.Core"
87-
>> CmdLine.appendPrefix "--define" "FABLE_LIBRARY"
88-
>> CmdLine.appendRaw "--noCache"
89-
// Target implementation can require additional arguments
90-
>> this.FableArgsBuilder
91-
92-
Command.Fable(args)
93-
94-
"Copy stage" |> toConsole
95-
this.CopyStage()
96-
97-
"Post Fable build stage" |> toConsole
98-
this.PostFableBuildStage()
75+
let matcher = new Matcher()
76+
matcher.AddIncludePatterns(inputPatterns)
77+
// Ignore well-known build output folders from MSBuild
78+
matcher.AddExcludePatterns([ "**/obj/**"; "**/bin/**" ])
79+
80+
let directoryInfo = DirectoryInfo(Path.Resolve())
81+
let dirWrapper = DirectoryInfoWrapper(directoryInfo)
82+
83+
let inputFiles =
84+
matcher.Execute(dirWrapper).Files
85+
|> Seq.map (fun file -> Path.Combine(directoryInfo.FullName, file.Path))
86+
|> Seq.toList
87+
88+
IncrementalBuild.run
89+
$"fable-library-%s{this.Language}"
90+
(defaultArg forceBuild false)
91+
inputFiles
92+
[ Path.Resolve(this.OutDir) ]
93+
(fun () ->
94+
"Cleaning build directory" |> toConsole
95+
96+
this.deleteDirectoryRobust buildDir
97+
98+
"Building Fable.Library" |> toConsole
99+
100+
let args =
101+
CmdLine.appendRaw sourceDir
102+
>> CmdLine.appendPrefix "--outDir" outDir
103+
>> CmdLine.appendPrefix "--fableLib" fableLibArg
104+
>> CmdLine.appendPrefix "--lang" language
105+
>> CmdLine.appendPrefix "--exclude" "Fable.Core"
106+
>> CmdLine.appendPrefix "--define" "FABLE_LIBRARY"
107+
>> CmdLine.appendRaw "--noCache"
108+
// Target implementation can require additional arguments
109+
>> this.FableArgsBuilder
110+
111+
Command.Fable(args)
112+
113+
"Copy stage" |> toConsole
114+
this.CopyStage()
115+
116+
"Post Fable build stage" |> toConsole
117+
this.PostFableBuildStage()
118+
)

src/Fable.Build/FableLibrary/Dart.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ type BuildFableLibraryDart() =
1111
Path.Combine("src", "fable-library-dart"),
1212
Path.Combine("temp", "fable-library-dart"),
1313
Path.Combine("temp", "fable-library-dart"),
14+
[
15+
Path.Combine("src", "fable-library-dart", "**", "*.dart")
16+
Path.Combine("src", "fable-library-dart", "**", "*.fs")
17+
],
1418
Path.Combine(".", "temp", "fable-library-dart")
1519
)
1620

src/Fable.Build/FableLibrary/Python.fs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,25 @@ open System.IO
44
open Fake.IO
55
open Build.Utils
66
open SimpleExec
7-
open BlackFox.CommandLine
87

9-
type BuildFableLibraryPython(?skipCore: bool) =
8+
type BuildFableLibraryPython(?skipCore: bool, ?postFableBuildStage: unit -> unit) =
109
inherit
1110
BuildFableLibrary(
1211
language = "python",
1312
libraryDir = Path.Combine("src", "fable-library-py"),
1413
sourceDir = Path.Combine("src", "fable-library-py", "fable_library"),
1514
buildDir = Path.Combine("temp", "fable-library-py"),
16-
outDir = Path.Combine("temp", "fable-library-py", "fable_library")
15+
outDir = Path.Combine("temp", "fable-library-py", "fable_library"),
16+
inputPatterns =
17+
[
18+
Path.Combine("src", "fable-library-py", "**", "*.py")
19+
Path.Combine("src", "fable-library-py", "**", "*.fs")
20+
Path.Combine("src", "fable-library-ts", "**", "*.fs")
21+
]
1722
)
1823

1924
let skipCore = defaultArg skipCore false
25+
let postFableBuildStage = defaultArg postFableBuildStage ignore
2026

2127
override this.CopyStage() =
2228
// Copy all Python/F# files to the build directory
@@ -53,3 +59,6 @@ type BuildFableLibraryPython(?skipCore: bool) =
5359
Command.Run("uv", $"run ruff check --select I,F401 --fix {this.BuildDir}")
5460
// Run Ruff formatter on all generated files
5561
Command.Run("uv", $"run ruff format {this.BuildDir}")
62+
63+
// Run the post fable buil stage if provided (used for quicktest to install the library in editable mode)
64+
postFableBuildStage ()

src/Fable.Build/FableLibrary/Rust.fs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ type BuildFableLibraryRust() =
1212
Path.Combine("src", "fable-library-rust"),
1313
Path.Combine("src", "fable-library-rust", "src"),
1414
Path.Combine("temp", "fable-library-rust"),
15-
Path.Combine("temp", "fable-library-rust", "src")
15+
Path.Combine("temp", "fable-library-rust", "src"),
16+
inputPatterns =
17+
[
18+
Path.Combine("src", "fable-library-rust", "**", "*.rs")
19+
Path.Combine("src", "fable-library-rust", "**", "*.fs")
20+
Path.Combine("src", "fable-library-rust", "Cargo.toml")
21+
]
1622
)
1723

1824
override this.PostFableBuildStage() =

src/Fable.Build/FableLibrary/TypeScript.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ type BuildFableLibraryTypeScript() =
1313
Path.Combine("src", "fable-library-ts"),
1414
Path.Combine("temp", "fable-library-ts"),
1515
Path.Combine("temp", "fable-library-ts"),
16+
[
17+
Path.Combine("src", "fable-library-ts", "**", "*.ts")
18+
Path.Combine("src", "fable-library-ts", "**", "*.fs")
19+
],
1620
Path.Combine(".", "temp", "fable-library-ts")
1721
)
1822

0 commit comments

Comments
 (0)