Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
014af4d
fix compilation and tests for most-recent F# compiler and dotnet SDK…
OlegZee Nov 8, 2019
3c0cd47
project cleanup
OlegZee Nov 10, 2019
f1300ac
Merge branch 'dev' of https://github.com/OlegZee/Xake into dev
OlegZee Nov 10, 2019
079c137
recompiled for netcore, dropped net 4.6 target, updated samples (refe…
OlegZee Feb 21, 2024
72d66cc
updated references to Xake 2.0.0
OlegZee Feb 21, 2024
476e0fc
improved error reporting, proper db termination. BROKEN exitcode is a…
OlegZee Feb 22, 2024
71a53da
fixed (restored) error processing (exit 2)
OlegZee Feb 22, 2024
aff62f4
fixed travis script
OlegZee Feb 22, 2024
41d5cbd
added GH Actions script
OlegZee Feb 22, 2024
f6658f1
Merge branch 'dev' into feature/netcore
OlegZee Feb 23, 2024
a1d299a
script for tags
OlegZee Feb 23, 2024
9a1c1d5
Merge pull request #1 from OlegZee/feature/netcore
OlegZee Feb 23, 2024
923e6ea
Update GitHub Actions workflows and .NET SDK version
oleg-zaimkin Jun 28, 2025
a9bee61
Rename workflows and update README with build status
oleg-zaimkin Jun 28, 2025
5cf8660
Enhance documentation and improve Shell command options in Xake scrip…
OlegZee Jun 29, 2025
2737a0c
Refactor publish workflow and enhance build logging
oleg-zaimkin Jun 29, 2025
b5797d4
Update upload-artifact action to v4 and adjust build log retention
oleg-zaimkin Jun 29, 2025
c48b71b
Refactor version extraction in publish workflow and update environmen…
oleg-zaimkin Jun 29, 2025
16faa91
Refactor version retrieval in build script to simplify logic and impr…
oleg-zaimkin Jun 29, 2025
cff78cd
Feature/resetdb command (#3)
OlegZee Mar 2, 2026
05c74b4
Update publish workflow to streamline build and publish
OlegZee Mar 2, 2026
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
24 changes: 24 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net

name: Build and test

on:
push:
branches: [ "dev" ]
pull_request:
branches: [ "dev" ]

jobs:
build:
if: github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 9.0.x
- name: Build & test
run: dotnet fsi build.fsx -- -- build test
43 changes: 43 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net

name: Publish package

on:
push:
tags:
- 'v*'

jobs:
publish:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 9.0.x

- name: Extract version from tag
id: version
run: |
TAG_NAME="${{ github.ref_name }}"
VERSION="${TAG_NAME#v}" # Remove 'v' prefix
echo "version=${VERSION}.${{ github.run_number }}" >> $GITHUB_OUTPUT

- name: Build and test
run: dotnet fsi build.fsx -- -- build test pack
env:
VERSION: ${{ steps.version.outputs.version }}

- name: Publish
run: dotnet nuget push out/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://www.nuget.org/api/v2/package

- name: Upload build log
uses: actions/upload-artifact@v4
if: always()
with:
name: build-log-${{ github.run_number }}.txt
path: build.log
retention-days: 5
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ _UpgradeReport*
packages
.nuget
.paket
.ionide

# Ignore Visual Studio files
*.pdb
Expand All @@ -32,11 +33,14 @@ TestResult.*
.xake*
.fake
.vs/
.vscode/
.ionide/
samples/**/*.exe
samples/**/*.dll
samples/**/*.fsx.lock
.idea
temp
.claude/settings.local.json
~testout~
paket-files/
/out
Expand Down
12 changes: 4 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
language: csharp
mono: latest
dotnet: 2.1.300
dotnet: 7.0.202
env: VER=$(if [[ "${TRAVIS_TAG:0:1}" == "v" ]]; then echo ${TRAVIS_TAG:1}.${TRAVIS_BUILD_NUMBER}; else echo 1.0.0.${TRAVIS_BUILD_NUMBER}; fi;)
install:
- dotnet restore build.proj
script:
- export FrameworkPathOverride=$(dirname $(which mono))/../lib/mono/4.5-api/
- dotnet fake run build.fsx -- build test -ll Diag
- dotnet fsi build.fsx -- -- build test -ll Diag
deploy:
- provider: script
script: dotnet fake run build.fsx -- pack push -ll Diag
script: dotnet fsi build.fsx -- -- pack push -ll Diag
skip_cleanup: true
on:
tags: true
condition: "${TRAVIS_TAG:0:1} = v"
condition: "${TRAVIS_TAG:0:1} = v"
5 changes: 0 additions & 5 deletions .vscode/settings.json

This file was deleted.

83 changes: 83 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Xake is an F# build utility inspired by Shake. It uses F# as a full programming language to define build rules with dependency tracking, incremental builds, and parallel execution. Xake is self-hosting — it builds itself using its own build script (`build.fsx`).

## Build & Test Commands

```bash
# Build and test (primary command)
dotnet fsi build.fsx -- -- build test

# Build core library only
dotnet build src/core

# Run all tests
dotnet test src/tests

# Run tests matching a filter
dotnet test src/tests --filter "Name~\"some pattern\""

# Build + filtered test via build script
dotnet fsi build.fsx -- -- build test -d FILTER=TestName

# Clean
dotnet fsi build.fsx -- -- clean
```

Requires .NET SDK 9.0+ (see `global.json`).

## Architecture

### Core Concepts

- **Target**: What a rule produces — either a `FileTarget` (file path) or `PhonyAction` (named action)
- **Rule**: Maps a target pattern to a Recipe. Types: `FileRule`, `MultiFileRule`, `PhonyRule`, `FileConditionRule`
- **Recipe**: Async-like computation expression (`recipe { ... }`) that builds a target and tracks dependencies
- **Dependency**: Tracked inputs — `FileDep`, `ArtifactDep`, `EnvVar`, `Var`, `AlwaysRerun`, `GetFiles`

### Execution Flow

```
Program.fs (CLI parsing) → ExecCore.runScript() → DependencyAnalysis →
WorkerPool (parallel execution) → Rule matching & Recipe execution → Database.fs (persist state)
```

### Key Source Files

| File | Role |
|------|------|
| `src/core/ExecCore.fs` | Main execution engine: rule matching, recipe execution, dependency tracking |
| `src/core/Database.fs` | Build state persistence for incremental builds |
| `src/core/ExecTypes.fs` | Configuration types (`ExecOptions`) and execution context |
| `src/core/DependencyAnalysis.fs` | Topological sorting and execution order |
| `src/core/WorkerPool.fs` | Thread pool for parallel rule execution |
| `src/core/Program.fs` | CLI argument parsing |
| `src/core/XakeScript.fs` | `xakeScript` computation expression builder |
| `src/core/RecipeBuilder.fs` | `recipe` computation expression builder |
| `src/core/Fileset.fs` | Ant-style file pattern matching with named capture groups |
| `src/core/Types.fs` | Domain types (Target, Rule, Recipe, Dependency, BuildResult) |

### Project Layout

- `src/core/` — Core library (`Xake.fsproj`), targets net462 + netstandard2.0
- `src/tests/` — NUnit tests, targets net9.0
- `build.fsx` — Self-hosting build script
- `docs/` — Documentation (overview, implementation notes, dev process)
- `samples/` — Example Xake scripts
- `.xake` — Binary build database file (do not commit)

## F# Patterns Used

- **Computation expressions** for both build scripts (`xakeScript { ... }`) and build actions (`recipe { ... }`)
- **Discriminated unions** extensively for Target, Rule, Dependency types
- **Pattern matching** on file paths with Ant-style globs and named capture groups (e.g., `"(dir:*)/file.(ext:*)"`)
- OS-aware path comparison (case-insensitive on Windows, ordinal on Unix)

## Development Workflow

- Feature branches merge to `dev` via PR with squash
- Releases: merge `dev` to `master`, tag with `v` prefix (triggers NuGet publish via GitHub Actions)
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2014 OlegZee
Copyright (c) 2014-2024 OlegZee

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
3 changes: 1 addition & 2 deletions build.cmd
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
@echo off
dotnet restore build.proj
dotnet fake run build.fsx -- build
dotnet fsi build.fsx -- -- build
119 changes: 44 additions & 75 deletions build.fsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,31 @@
#r "paket:
nuget Xake ~> 1.1 prerelease //"

#if !FAKE
#load ".fake/build.fsx/intellisense.fsx"
#endif
#r "nuget: Xake, 2.2.0"

open Xake
open Xake.Tasks

let frameworks = ["netstandard2.0"; "net46"]
let frameworks = ["netstandard2.0" (*; "net46" *)]
let libtargets =
[ for t in frameworks do
for e in ["dll"; "xml"]
-> sprintf "out/%s/Xake.%s" t e
[ for fwk in frameworks do
for ext in ["dll"; "xml"]
-> $"out/%s{fwk}/Xake.%s{ext}"
]

let getVersion () = recipe {
let! verVar = getVar("VER")
let! verEnv = getEnv("VER")
let ver = verVar |> Option.defaultValue (verEnv |> Option.defaultValue "0.0.1")

let! verSuffix =
getVar("SUFFIX")
|> Recipe.map (
function
| None -> "-beta"
| Some "" -> "" // this is release!
| Some s -> "-" + s
)
return ver + verSuffix
}
let getVersion () = getEnv "VERSION" |> map (Option.defaultValue "0.0.1")

let makePackageName = sprintf "Xake.%s.nupkg"
let makePackageName version = $"Xake.%s{version}.nupkg"

let dotnet arglist = recipe {
do! shell {
let dotnet arglist =
shell {
cmd "dotnet"
args arglist
failonerror
} |> Recipe.Ignore
}
} |> Ignore

do xakeScript {
filelog "build.log" Verbosity.Diag
// consolelog Verbosity.Normal
filelog "build.log" Diag

rules [
"main" => recipe {
do! need ["build"]
do! need ["test"]
}
"main" <<< ["build"; "test"]

"build" <== libtargets
"clean" => rm {dir "out"}
Expand All @@ -58,38 +34,33 @@ do xakeScript {
do! alwaysRerun()

let! where =
getVar("FILTER")
|> Recipe.map (function |Some clause -> ["--filter"; sprintf "Name~\"%s\"" clause] | None -> [])

// in case of travis only run tests for standard runtime, eventually will add more
let! limitFwk = getEnv("TRAVIS") |> Recipe.map (function | Some _ -> ["-f:netcoreapp2.0"] | _ -> [])
getVar "FILTER"
|> map (function |Some clause -> ["--filter"; $"Name~\"{clause}\""] | None -> [])

do! dotnet <| ["test"; "src/tests"; "-c"; "Release"] @ where @ limitFwk
do! dotnet <| ["test"; "src/tests"; "-c"; "Release"] @ where
}

libtargets *..> recipe {

let! allFiles
= getFiles <| fileset {
basedir "src/core"
includes "Xake.fsproj"
includes "**/*.fs"
}
let! allFiles = getFiles <| fileset {
basedir "src/core"
includes "Xake.fsproj"
includes "**/*.fs"
}

do! needFiles allFiles
let! version = getVersion()

for framework in frameworks do
do! dotnet
[
"build"
"src/core"
"/p:Version=" + version
"--configuration"; "Release"
"--framework"; framework
"--output"; "../../out/" + framework
"/p:DocumentationFile=Xake.xml"
]
do! dotnet [
"build"
"src/core"
"/p:Version=" + version
"--configuration"; "Release"
"--framework"; framework
"--output"; "./out/" + framework
"/p:DocumentationFile=Xake.xml"
]
}
]

Expand All @@ -101,29 +72,27 @@ do xakeScript {
}

"out/Xake.(ver:*).nupkg" ..> recipe {
let! ver = getRuleMatch("ver")
do! dotnet
[
"pack"; "src/core"
"-c"; "Release"
"/p:Version=" + ver
"--output"; "../../out/"
"/p:DocumentationFile=Xake.xml"
]
let! ver = getRuleMatch "ver"
do! dotnet [
"pack"; "src/core"
"-c"; "Release"
$"/p:Version={ver}"
"--output"; "out/"
"/p:DocumentationFile=Xake.xml"
]
}

// push need pack to be explicitly called in advance
"push" => recipe {
let! version = getVersion()

let! nuget_key = getEnv("NUGET_KEY")
do! dotnet
[
"nuget"; "push"
"out" </> makePackageName version
"--source"; "https://www.nuget.org/api/v2/package"
"--api-key"; nuget_key |> Option.defaultValue ""
]
let! nuget_key = getEnv "NUGET_KEY"
do! dotnet [
"nuget"; "push"
"out" </> makePackageName version
"--source"; "https://www.nuget.org/api/v2/package"
"--api-key"; nuget_key |> Option.defaultValue ""
]
}
]
}
7 changes: 0 additions & 7 deletions build.fsx.lock

This file was deleted.

Loading