Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
44 changes: 44 additions & 0 deletions .build/checks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
configuration="${FSCRIPT_BUILD_CONFIG:-Release}"
cli=(dotnet "src/FScript/bin/$configuration/net10.0/fscript.dll")
run_node_tests() {
local report=$1
shift
mkdir -p "$(dirname "$report")"
node --test --test-reporter=spec --test-reporter-destination=stdout \
--test-reporter=junit --test-reporter-destination="$report" "$@"
}
case "${1:?Expected debugger, javascript, vscode, reports, or smoke}" in
debugger)
run_node_tests tests/FScript.DebugAdapter.Tests/TestResults/debugger.xml tests/FScript.DebugAdapter.Tests/*Tests.js
;;
javascript)
cd src/FScript.JavaScript
run_node_tests TestResults/javascript.xml test/*.test.mjs
;;
vscode)
cd vscode-fscript
run_node_tests TestResults/vscode.xml ../tests/FScript.LanguageServer.Tests/*Tests.js
;;
reports)
find src tests samples vscode-fscript \
-type d \( -name bin -o -name obj -o -name node_modules \) -prune -o \
-type f -path '*/TestResults/*' \( -name '*.trx' -o -name '*.xml' \) -print
;;
smoke)
for script in samples/*.fss; do
echo "Running $script"
"${cli[@]}" "$script" > /dev/null
done
"${cli[@]}" samples/basic/basic.fss -- helloworld.bas > /dev/null
"${cli[@]}" samples/debugging/main.fss > /dev/null
"${cli[@]}" samples/ffi/main.fss -- --headless
for script in samples/task-sharing/*.fss; do "${cli[@]}" analyze "$script"; done
"${cli[@]}" samples/task-sharing/ordered.fss > /dev/null
"${cli[@]}" --no-result samples/task-sharing/thread-safe.fss > /dev/null
echo "All smoke tests passed."
;;
*) echo "Unknown check: $1" >&2; exit 2 ;;
esac
11 changes: 11 additions & 0 deletions .build/ci-shards.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"include": [
{"name": "core", "projects": ["ci_core", "ci_ffi_managed"]},
{"name": "backend", "projects": ["ci_backend"]},
{"name": "cli", "projects": ["ci_cli"]},
{"name": "ffi-default", "projects": ["ci_ffi_default"]},
{"name": "ffi-size", "projects": ["ci_ffi_size"]},
{"name": "web", "projects": ["ci_web", "ci_website"]},
{"name": "integration", "projects": ["ci_integration"]}
]
}
11 changes: 11 additions & 0 deletions .build/ci/backend/PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project ci_backend {
depends_on = [ project.fscript_backend_dotnet_tests ]
includes = [ "../../checks.sh" ]
}

target test {
build = ~always
artifacts = ~none
depends_on = [ target.^test ]
workflow run { script = "../../checks.sh" args = "reports" }
}
11 changes: 11 additions & 0 deletions .build/ci/cli/PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project ci_cli {
depends_on = [ project.fscript_cli_tests ]
includes = [ "../../checks.sh" ]
}

target test {
build = ~always
artifacts = ~none
depends_on = [ target.^test ]
workflow run { script = "../../checks.sh" args = "reports" }
}
11 changes: 11 additions & 0 deletions .build/ci/core/PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project ci_core {
depends_on = [ project.fscript_language_tests project.fscript_runtime_tests project.fscript_compiler_tests project.fscript_conformance_tests project.fscript_interpreter_lir_tests project.fscript_languageserver_tests ]
includes = [ "../../checks.sh" ]
}

target test {
build = ~always
artifacts = ~none
depends_on = [ target.^test ]
workflow run { script = "../../checks.sh" args = "reports" }
}
11 changes: 11 additions & 0 deletions .build/ci/ffi-default/PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project ci_ffi_default {
depends_on = [ project.ffisample_tests ]
includes = [ "../../checks.sh" ]
}

target test {
build = ~always
artifacts = ~none
depends_on = [ target.^test_ffi_default ]
workflow run { script = "../../checks.sh" args = "reports" }
}
11 changes: 11 additions & 0 deletions .build/ci/ffi-managed/PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project ci_ffi_managed {
depends_on = [ project.ffisample_tests ]
includes = [ "../../checks.sh" ]
}

target test {
build = ~always
artifacts = ~none
depends_on = [ target.^test_ffi_managed ]
workflow run { script = "../../checks.sh" args = "reports" }
}
11 changes: 11 additions & 0 deletions .build/ci/ffi-size/PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project ci_ffi_size {
depends_on = [ project.ffisample_tests ]
includes = [ "../../checks.sh" ]
}

target test {
build = ~always
artifacts = ~none
depends_on = [ target.^test_ffi_size ]
workflow run { script = "../../checks.sh" args = "reports" }
}
11 changes: 11 additions & 0 deletions .build/ci/integration/PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project ci_integration {
depends_on = [ project.checks ]
includes = [ "../../checks.sh" ]
}

target test {
build = ~always
artifacts = ~none
depends_on = [ target.^test target.^smoke target.^sdk ]
workflow run { script = "../../checks.sh" args = "reports" }
}
11 changes: 11 additions & 0 deletions .build/ci/web/PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project ci_web {
depends_on = [ project.javascript project.vscode ]
includes = [ "../../checks.sh" ]
}

target test {
build = ~always
artifacts = ~none
depends_on = [ target.^test ]
workflow run { script = "../../checks.sh" args = "reports" }
}
11 changes: 11 additions & 0 deletions .build/ci/website/PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
project ci_website {
depends_on = [ project.website ]
includes = [ "../../checks.sh" ]
}

target test {
build = ~always
artifacts = ~none
depends_on = [ target.^test_ci ]
workflow run { script = "../../checks.sh" args = "reports" }
}
25 changes: 25 additions & 0 deletions .build/read-tool-versions.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import fs from 'node:fs';
import { pathToFileURL } from 'node:url';

export function readToolVersions(source) {
const blocks = [...source.matchAll(/\bversions\s*=\s*\{([^}]*)\}/g)];
if (blocks.length !== 1) throw new Error('WORKSPACE must define one locals.versions block.');
const versions = {};
for (const line of blocks[0][1].split('\n')) {
if (/^\s*(?:#.*)?$/.test(line)) continue;
const match = line.match(/^\s*([a-z][a-z0-9_]*)\s*:\s*"([a-zA-Z0-9][a-zA-Z0-9.+_-]*)"\s*(?:#.*)?$/);
if (!match) throw new Error(`Tool versions must be quoted literal values: ${line.trim()}`);
const [, name, value] = match;
if (Object.hasOwn(versions, name)) throw new Error(`Duplicate tool version: ${name}`);
versions[name] = value;
}
for (const name of ['dotnet_sdk', 'node', 'terrabuild', 'vsce', 'ovsx']) {
if (!versions[name]) throw new Error(`Missing tool version: ${name}`);
}
return versions;
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const versions = readToolVersions(fs.readFileSync('WORKSPACE', 'utf8'));
for (const [name, value] of Object.entries(versions)) console.log(`${name}=${value}`);
}
13 changes: 13 additions & 0 deletions .build/reports/PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
project test_reports {
depends_on = [ project.fscript_language_tests project.fscript_runtime_tests
project.fscript_compiler_tests project.fscript_conformance_tests
project.fscript_interpreter_lir_tests project.fscript_backend_dotnet_tests
project.fscript_cli_tests project.fscript_languageserver_tests
project.ffisample_tests project.javascript project.vscode project.checks ]
includes = [ "../checks.sh" ]
}

# An executing consumer restores cached test outputs before GitHub collects them.
target reports {
workflow run { script = "../checks.sh" args = "reports" }
}
21 changes: 21 additions & 0 deletions .build/run-terrabuild.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
mkdir -p .terrabuild
report=.terrabuild/result.json
rm -f "$report"
parallel_args=()
if [[ -n "${TB_PARALLEL:-}" ]]; then
parallel_args=(--parallel "$TB_PARALLEL")
fi
group_args=()
if [[ -n "${TERRABUILD_GROUP:-}" ]]; then
group_args=(--group "$TERRABUILD_GROUP")
fi
terrabuild run "$@" --variable "config=${FSCRIPT_BUILD_CONFIG:-Release}" ${parallel_args[@]+"${parallel_args[@]}"} ${group_args[@]+"${group_args[@]}"} --out "$report" 2>&1 | tee .terrabuild/run.log
# Terrabuild 0.200.1 can exit zero after reporting a failed graph. Require the
# machine-readable completion status too, including for a fully cached run.
if ! grep -Eq '"status"[[:space:]]*:[[:space:]]*"success"' "$report"; then
echo "Terrabuild did not report a successful build; inspect the task logs above." >&2
exit 1
fi
20 changes: 20 additions & 0 deletions .build/sdk-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Publishing different assembly versions must not mutate outputs used by tests.
set -euo pipefail
cd "$(dirname "$0")/.."
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
node - "$work" <<'JS'
const fs = require('node:fs');
const path = require('node:path');
const {execFileSync} = require('node:child_process');
const destination = process.argv[2];
const files = execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], {maxBuffer: 16 * 1024 * 1024}).toString().split('\0').filter(Boolean);
for (const file of new Set(files)) {
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) continue;
const output = path.join(destination, file);
fs.mkdirSync(path.dirname(output), {recursive: true});
fs.copyFileSync(file, output);
}
JS
bash "$work/.github/scripts/verify-sdk-provider.sh"
23 changes: 23 additions & 0 deletions .build/toolchain/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# check=skip=InvalidDefaultArgInFrom
# Versions are required build arguments supplied by WORKSPACE.
ARG NODE_VERSION
ARG DOTNET_SDK_VERSION
FROM node:${NODE_VERSION}-bookworm-slim AS node
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_SDK_VERSION}

LABEL org.opencontainers.image.source="https://github.com/MagnusOpera/FScript"

# AOT integration tests invoke the native compiler. Node is also needed by the
# debugger tests, Fable package, VS Code extension, and documentation build.
RUN apt-get update \
&& apt-get install -y --no-install-recommends clang zlib1g-dev git ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
&& ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1 NPM_CONFIG_AUDIT=false NPM_CONFIG_FUND=false

# Tests capture subprocess output. Persistent SDK/compiler servers inherit those
# pipes and can keep ReadToEnd waiting after the child command has exited.
ENV MSBUILDDISABLENODEREUSE=1 DOTNET_CLI_USE_MSBUILD_SERVER=0 UseSharedCompilation=false
17 changes: 17 additions & 0 deletions .build/toolchain/PROJECT
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
project toolchain {
labels = [ "toolchain" ]
includes = [ "**/*" "../../WORKSPACE" ]
@docker { }
}

target dist {
phase = phase.toolchains
@docker build {
# GHCR returned missing child manifests for the attested index.
args = "--provenance=false"
build_args = {
DOTNET_SDK_VERSION: local.versions.dotnet_sdk
NODE_VERSION: local.versions.node
}
}
}
12 changes: 12 additions & 0 deletions .build/workflow.fss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Build/test scripts share a pinned container and can cache successful results.
// @shell is reserved for the host-side Docker image bootstrap.
export let run (context: Protocol.ActionContext) (script: string) (args: string option) =
let arguments = Option.defaultValue "" args
{ PreserveProvenance = Broken
Batchable = false
Operations =
[ { Command = "bash"
Arguments = $"{script} {arguments}"
ErrorLevel = 0 } ] }

{ [nameof run] = [Cache Remote] }
51 changes: 51 additions & 0 deletions .github/actions/collect-build-results/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Collect build results
description: Upload test results and Terrabuild logs, and publish GitHub test summaries.

inputs:
suffix:
description: Artifact and report suffix for this workflow
required: true

publish:
description: Publish reports immediately instead of deferring to the CI join job
default: 'true'

runs:
using: composite
steps:
- name: Collect current test results
if: always()
shell: bash
run: node .github/scripts/collect-test-results.mjs

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ inputs.suffix }}
path: |
.out/test-results/**/*.trx
.out/test-results/**/*.xml
include-hidden-files: true
if-no-files-found: warn

- name: Upload Terrabuild logs
if: always()
uses: actions/upload-artifact@v4
with:
name: terrabuild-logs-${{ inputs.suffix }}
path: |
.terrabuild/result.json
.terrabuild/run.log
terrabuild-debug.*
~/.terrabuild/cache/**/status
~/.terrabuild/cache/**/summary.json
~/.terrabuild/cache/**/*.log
include-hidden-files: true
if-no-files-found: warn

- name: Publish test summaries
if: always() && inputs.publish == 'true'
uses: ./.github/actions/publish-test-results
with:
suffix: ${{ inputs.suffix }}
34 changes: 34 additions & 0 deletions .github/actions/publish-test-results/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Publish test results
description: Publish .NET and Node test summaries from the selected report directory.
inputs:
suffix:
description: Names used for GitHub check reports
required: true
path:
description: Root containing TRX and JUnit reports
default: .out/test-results
runs:
using: composite
steps:
- name: Publish .NET test report
if: always()
continue-on-error: true
uses: magnusopera/test-reporter@main
with:
name: .NET Tests (${{ inputs.suffix }})
path: '${{ inputs.path }}/**/*.trx'
reporter: dotnet-trx
fail-on-error: false
fail-on-empty: false
use-actions-summary: true
- name: Publish JavaScript and debugger test report
if: always()
continue-on-error: true
uses: magnusopera/test-reporter@main
with:
name: JavaScript and Debugger Tests (${{ inputs.suffix }})
path: '${{ inputs.path }}/**/*.xml'
reporter: java-junit
fail-on-error: false
fail-on-empty: false
use-actions-summary: true
Loading
Loading