diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 0f7926bad2..9e89cf8b69 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -2,8 +2,8 @@ "version": 1, "isRoot": true, "tools": { - "fantomas-tool": { - "version": "4.7.9", + "fantomas": { + "version": "6.2.0", "commands": [ "fantomas" ] diff --git a/.editorconfig b/.editorconfig index 0c4f918b4a..a9fb857993 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,5 +9,7 @@ indent_style = space indent_size = 2 # Fantomas (see https://github.com/fsprojects/fantomas/blob/master/docs/Documentation.md) -[*.fs] -max_line_length=140 +[*.{fs,fsx,fsi}] +max_line_length = 80 +fsharp_multi_line_lambda_closing_newline = true +fsharp_multiline_bracket_style = stroustrup \ No newline at end of file diff --git a/.fantomasignore b/.fantomasignore new file mode 100644 index 0000000000..ad7e854c86 --- /dev/null +++ b/.fantomasignore @@ -0,0 +1,4 @@ +src/** +tests/** +build/** +tests_external/** \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 58410f3558..e6305e3adf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,5 +27,6 @@ "build/fable-library-rust/Cargo.toml", "build/tests/Rust/Cargo.toml" ], - "editor.inlayHints.enabled": "offUnlessPressed" + "editor.inlayHints.enabled": "offUnlessPressed", + "dotnet.defaultSolution": "disable" } diff --git a/Build.fsproj b/Build.fsproj new file mode 100644 index 0000000000..48356ed0ee --- /dev/null +++ b/Build.fsproj @@ -0,0 +1,30 @@ + + + Exe + net6.0 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Fable.sln b/Fable.sln index 550a366e9b..bbe3ccedf3 100644 --- a/Fable.sln +++ b/Fable.sln @@ -60,6 +60,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Fable.Tests.TypeScript", "t EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fable.Tests.Spaces", "tests\Js\Project With Spaces\Fable.Tests.Spaces.fsproj", "{C90E23AF-4B5B-44A7-ADCC-3BF89547395B}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Build", "Build.fsproj", "{EEBF506A-F0BC-4660-9387-65ADA5F36EAA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -154,6 +156,10 @@ Global {C90E23AF-4B5B-44A7-ADCC-3BF89547395B}.Debug|Any CPU.Build.0 = Debug|Any CPU {C90E23AF-4B5B-44A7-ADCC-3BF89547395B}.Release|Any CPU.ActiveCfg = Release|Any CPU {C90E23AF-4B5B-44A7-ADCC-3BF89547395B}.Release|Any CPU.Build.0 = Release|Any CPU + {EEBF506A-F0BC-4660-9387-65ADA5F36EAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EEBF506A-F0BC-4660-9387-65ADA5F36EAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EEBF506A-F0BC-4660-9387-65ADA5F36EAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EEBF506A-F0BC-4660-9387-65ADA5F36EAA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/build copy.fsx b/build copy.fsx new file mode 100644 index 0000000000..349eb4f238 --- /dev/null +++ b/build copy.fsx @@ -0,0 +1,893 @@ +#load "src/Fable.PublishUtils/PublishUtils.fs" + +open System +open System.Text.RegularExpressions +open PublishUtils + +// ncave FCS fork +let FCS_REPO = "https://github.com/ncave/fsharp" +let FCS_REPO_LOCAL = "../fsharp_fable" +let FCS_REPO_FABLE_BRANCH = "fable" +let FCS_REPO_SERVICE_SLIM_BRANCH = "service_slim" + +let BUILD_ARGS = + fsi.CommandLineArgs + |> Array.skip 1 + |> List.ofArray + +let BUILD_ARGS_LOWER = + BUILD_ARGS + |> List.map (fun x -> x.ToLower()) + +module Util = + let cleanDirs dirs = + for dir in dirs do + printfn $"Clean {dir}" + removeDirRecursive dir + + // TODO: move to PublishUtils.fs ? + let copyFiles sourceDir searchPattern destDir = + printfn $"Copy {sourceDir searchPattern} to {destDir}" + for source in IO.Directory.GetFiles(sourceDir, searchPattern) do + let fileName = IO.Path.GetFileName(source) + let target = destDir fileName + IO.File.Copy(source, target, true) + + let resolveDir dir = + __SOURCE_DIRECTORY__ dir + + let updateVersionsInFableTransforms compilerVersion (libraryVersions: (string * string) list) = + let mutable updated = Set.empty + + let replaceVersion (lang: string option) version fileContent = + let prefix = + match lang with + | None -> "" + | Some lang -> $"{lang.ToUpperInvariant()}_LIBRARY_" + + Regex.Replace( + fileContent, + $@"^(\s*)let \[] {prefix}VERSION = ""(.*?)""", + (fun (m: Match) -> + match lang with + | Some lang when m.Groups[2].Value <> version -> + updated <- Set.add lang updated + | _ -> () + m.Groups[1].Value + $"let [] {prefix}VERSION = \"{version}\"" + ), + RegexOptions.Multiline) + + let filePath = "src/Fable.Transforms/Global/Compiler.fs" + readFile filePath + |> replaceVersion None compilerVersion + |> List.foldBack (fun (lang, version) fileContent -> + replaceVersion (Some lang) version fileContent) libraryVersions + |> writeFile filePath + + updated + + let updatePkgVersionInFsproj projFile version = + readFile projFile + |> replaceRegex Publish.NUGET_PACKAGE_VERSION (fun m -> + m.Groups[1].Value + version + m.Groups[3].Value) + |> writeFile projFile + + let runTSLint projectDir = + run ("npm run tslint -- --project " + projectDir) + + let runTypeScript projectDir = + run ("npm run tsc -- --project " + projectDir) + + let runTypeScriptWithArgs projectDir args = + run ("npm run tsc -- --project " + projectDir + " " + String.concat " " args) + + let runFableWithArgs projectDir args = + run ("dotnet run -c Release --project src/Fable.Cli -- " + projectDir + " " + String.concat " " args) + + let watchFableWithArgs projectDir args = + run ("dotnet watch --project src/Fable.Cli run -- " + projectDir + " --cwd ../.. " + String.concat " " args) + + let runFableWithArgsInDirAs release projectDir args = + let cliDir = resolveDir "src/Fable.Cli" + let cliArgs = args |> String.concat " " + let cliCmd = $"""dotnet run {if release then "-c Release" else ""} --project {cliDir} -- {cliArgs}""" + runInDir (resolveDir projectDir) cliCmd + + let runFableWithArgsInDir projectDir args = + runFableWithArgsInDirAs true projectDir args + + let runFableWithArgsAsync projectDir args = + runAsync ("dotnet run -c Release --project src/Fable.Cli -- " + projectDir + " " + String.concat " " args) + + let runNpx command args = + run ("npx " + command + " " + (String.concat " " args)) + + let runNpmScript script args = + run ("npm run " + script + " -- " + (String.concat " " args)) + + let runNpmScriptAsync script args = + runAsync ("npm run " + script + " -- " + (String.concat " " args)) + + let runFable projectDir = + runFableWithArgs projectDir [] + + let runMocha testDir = + runNpmScript "mocha" [$"{testDir} --reporter dot -t 10000"] + +open Util + +module Unused = + let downloadAndExtractTo (url: string) (targetDir: string) = + $"npx download --extract --out %s{targetDir} \"%s{url}\"" |> run + + let coverage() = + // report converter + // https://github.com/danielpalme/ReportGenerator + // dotnet tool install dotnet-reportgenerator-globaltool --tool-path tools + if not (pathExists "./bin/tools/reportgenerator") && not (pathExists "./bin/tools/reportgenerator.exe") then + runInDir "." "dotnet tool install dotnet-reportgenerator-globaltool --tool-path bin/tools" + let reportGen = + if pathExists "./bin/tools/reportgenerator" then "bin/tools/reportgenerator" + else "bin\\tools\\reportgenerator.exe" + + // if not (pathExists "build/fable-library") then + // buildLibrary() + + cleanDirs ["build/tests"] + runFable "tests" + + // JS + run "npx nyc mocha build/tests --require source-map-support/register --reporter dot -t 10000" + runInDir "." (reportGen + " \"-reports:build/coverage/nyc/lcov.info\" -reporttypes:Html \"-targetdir:build/coverage/nyc/html\" ") + + // .NET + //runInDir "tests/Main" "dotnet build /t:Collect_Coverage" + cleanDirs ["build/coverage/netcoreapp2.0/out"] + runInDir "." (reportGen + " \"-reports:build/coverage/netcoreapp2.0/coverage.xml\" -reporttypes:Html \"-targetdir:build/coverage/netcoreapp2.0/html\" ") + +// TARGETS --------------------------- + +// let buildLibraryJsWithOptions (opts: {| watch: bool |}) = +// let baseDir = __SOURCE_DIRECTORY__ + +// let projectDir = baseDir "src/fable-library" +// let buildDir = baseDir "build/fable-library" +// let fableOpts = [ +// "--outDir " + buildDir +// "--fableLib " + buildDir +// "--exclude Fable.Core" +// "--define FX_NO_BIGINT" +// "--define FABLE_LIBRARY" +// if opts.watch then "--watch" +// ] + +// cleanDirs [buildDir] +// runInDir baseDir "npm install" +// makeDirRecursive buildDir + +// copyFile (projectDir "package.json") buildDir + +// if opts.watch then +// Async.Parallel [ +// runNpmScriptAsync "tsc" [ +// "--project " + projectDir +// "--watch" +// ] +// runFableWithArgsAsync projectDir fableOpts +// ] |> runAsyncWorkflow +// else +// runTSLint projectDir +// runTypeScript projectDir +// runFableWithArgs projectDir fableOpts +// removeDirRecursive (buildDir ".fable") + +// let buildLibraryJs() = buildLibraryJsWithOptions {| watch = false |} + +// let buildLibraryJsIfNotExists() = +// if not (pathExists (__SOURCE_DIRECTORY__ "build/fable-library")) then +// buildLibraryJs() + +let buildLibraryTs() = + let baseDir = __SOURCE_DIRECTORY__ + let sourceDir = "./src/fable-library" + let buildDirTs = "./build/fable-library-ts" + let buildDirJs = "./build/fable-library" + + cleanDirs [buildDirTs; buildDirJs] + runInDir baseDir "npm install" + + runFableWithArgs sourceDir [ + "--outDir " + buildDirTs + "--fableLib " + buildDirTs + "--lang TypeScript" + "--typedArrays false" + "--exclude Fable.Core" + "--define FX_NO_BIGINT" + "--define FABLE_LIBRARY" + ] + + copyFiles sourceDir "*.ts" buildDirTs + copyFiles (sourceDir "ts") "*.json" buildDirTs + copyDirRecursive (sourceDir "lib") (buildDirTs "lib") + copyFile (sourceDir "package.json") buildDirTs + + // runTSLint buildDirTs + runTypeScriptWithArgs buildDirTs ["--outDir " + buildDirJs] + copyFile (buildDirTs "lib/big.d.ts") (buildDirJs "lib/big.d.ts") + copyFile (buildDirTs "package.json") buildDirJs + + copyFile (sourceDir "README.md") buildDirJs + +let buildLibraryTsIfNotExists() = + if not (pathExists (__SOURCE_DIRECTORY__ "build/fable-library-ts")) then + buildLibraryTs() + +let buildLibraryPy() = + let libraryDir = "./src/fable-library-py" + let projectDir = libraryDir "fable_library" + let buildDirPy = "./build/fable-library-py" + + cleanDirs [buildDirPy] + + runFableWithArgs projectDir [ + "--outDir " + buildDirPy "fable_library" + "--fableLib " + buildDirPy "fable_library" + "--lang Python" + "--exclude Fable.Core" + "--define FABLE_LIBRARY" + ] + + // Copy python related files from projectDir to buildDir + copyFiles libraryDir "*" buildDirPy + copyFiles projectDir "*.py" (buildDirPy "fable_library") + + // Fix issues with Fable .fsproj not supporting links + copyDirNonRecursive (buildDirPy "fable_library/fable-library") (buildDirPy "fable_library") + removeDirRecursive (buildDirPy "fable_library/fable-library") + +let buildLibraryPyIfNotExists() = + let baseDir = __SOURCE_DIRECTORY__ + if not (pathExists (baseDir "build/fable-library-py")) then + buildLibraryPy() + +let buildLibraryRust() = + let libraryDir = "src/fable-library-rust" + let sourceDir = libraryDir "src" + let buildDir = "build/fable-library-rust" + let outDir = buildDir "src" + let fableLib = "." + + cleanDirs [buildDir] + + runFableWithArgsInDir sourceDir [ + "--outDir " + resolveDir outDir + "--fableLib " + fableLib + "--lang Rust" + "--exclude Fable.Core" + "--define FABLE_LIBRARY" + "--noCache" + ] + + copyFiles libraryDir "*.toml" buildDir + copyFiles sourceDir "*.rs" outDir + copyDirRecursive (libraryDir "vendored") (buildDir "vendored") + + runInDir buildDir "cargo fmt" + runInDir buildDir "cargo fix --allow-no-vcs" + runInDir buildDir "cargo build" + +let buildLibraryRustIfNotExists() = + if not (pathExists (__SOURCE_DIRECTORY__ "build/fable-library-rust")) then + buildLibraryRust() + +let buildLibraryDart(clean: bool) = + let sourceDir = resolveDir "src/fable-library-dart" + let buildDir = resolveDir "build/fable-library-dart" + + if clean then + cleanDirs [buildDir] + makeDirRecursive buildDir + copyFiles sourceDir "pubspec.*" buildDir + copyFiles sourceDir "analysis_options.yaml" buildDir + + copyFiles sourceDir "*.dart" buildDir + + runFableWithArgsInDir sourceDir [ + "--outDir " + buildDir + "--fableLib " + buildDir + "--lang Dart" + "--exclude Fable.Core" + "--define FABLE_LIBRARY" + if clean then "--noCache" + ] + +let buildLibraryDartIfNotExists() = + if not (pathExists (__SOURCE_DIRECTORY__ "build/fable-library-dart")) then + buildLibraryDart(true) + +// Like testStandalone() but doesn't create bundles/packages for fable-standalone & friends +// Mainly intended for CI +let testStandaloneFast() = + runFableWithArgs "src/fable-standalone/src" [ + "--noCache" + ] + + runFableWithArgs "src/fable-compiler-js/src" [ + "--exclude Fable.Core" + "--define LOCAL_TEST" + ] + + let fableJs = "./src/fable-compiler-js/src/app.fs.js" + let testProj = "tests/Js/Main/Fable.Tests.fsproj" + let buildDir = "build/tests/Standalone" + run $"node {fableJs} {testProj} {buildDir}" + runMocha buildDir + +let buildWorker (opts: {| minify: bool; watch: bool |}) = + printfn "Building worker%s..." (if opts.minify then "" else " (no minification)") + + let projectDir = "src/fable-standalone/src" + let buildDir = "build/fable-standalone" + let distDir = "src/fable-standalone/dist" + + runFableWithArgs (projectDir + "/Worker") [ + "--outDir " + buildDir + "/worker" + ] + + let rollupTarget = + match opts.minify with + | true -> buildDir "worker.js" + | false -> distDir "worker.min.js" + + // make standalone worker dist + runNpmScript "rollup" [$"""{buildDir}/worker/Worker.js -o {rollupTarget} --format iife"""] + + if opts.minify then + // runNpx "webpack" [sprintf "--entry ./%s/worker.js --output ./%s/worker.min.js --config ./%s/../worker.config.js" buildDir distDir projectDir] + runNpmScript "terser" [$"{buildDir}/worker.js -o {distDir}/worker.min.js --mangle --compress"] + + // Put fable-library files next to bundle + printfn "Copying fable-library..." + buildLibraryTsIfNotExists() + let libraryDir = "build/fable-library" + let libraryTarget = distDir "fable-library" + copyDirRecursive libraryDir libraryTarget + +let buildStandalone (opts: {| minify: bool; watch: bool |}) = + buildLibraryTs() + + printfn "Building standalone%s..." (if opts.minify then "" else " (no minification)") + + let projectDir = "src/fable-standalone/src" + let buildDir = "build/fable-standalone" + let distDir = "src/fable-standalone/dist" + + let rollupTarget = + match opts.watch, opts.minify with + | true, _ -> + match BUILD_ARGS with + | _::rollupTarget::_ -> rollupTarget + | _ -> failwith "Pass the bundle output, e.g.: npm run build watch-standalone ../repl3/public/js/repl/bundle.min.js" + | false, true -> buildDir "bundle.js" + | false, false -> distDir "bundle.min.js" + + let rollupArgs = [ + buildDir "bundle/Main.js" + "-o " + rollupTarget + "--format umd" + "--name __FABLE_STANDALONE__" + ] + + // cleanup + if not opts.watch then + cleanDirs [buildDir; distDir] + makeDirRecursive distDir + + // build standalone bundle + runFableWithArgs projectDir [ + "--outDir " + buildDir "bundle" + if opts.watch then + "--watch" + "--run rollup" + yield! rollupArgs + "--watch" + ] + + // make standalone bundle dist + runNpmScript "rollup" rollupArgs + if opts.minify then + runNpmScript "terser" [ + buildDir "bundle.js" + "-o " + distDir "bundle.min.js" + "--mangle" + "--compress" + ] + + // build standalone worker + buildWorker opts + + // print bundle size + fileSizeInBytes (distDir "bundle.min.js") / 1000. |> printfn "Bundle size: %fKB" + fileSizeInBytes (distDir "worker.min.js") / 1000. |> printfn "Worker size: %fKB" + +let buildCompilerJs(minify: bool) = + let projectDir = "src/fable-compiler-js/src" + let buildDir = "build/fable-compiler-js" + let distDir = "src/fable-compiler-js/dist" + + if not (pathExists "build/fable-standalone") then + buildStandalone {|minify=minify; watch=false|} + + cleanDirs [buildDir; distDir] + makeDirRecursive distDir + + runFableWithArgs projectDir [ + "--outDir " + buildDir + "--exclude Fable.Core" + ] + + let rollupTarget = if minify then distDir "app.js" else distDir "app.min.js" + run $"npx rollup {buildDir}/app.js -o {rollupTarget} --format umd --name Fable" + if minify then + run $"npx terser {distDir}/app.js -o {distDir}/app.min.js --mangle --compress" + + // Copy fable-library + copyDirRecursive ("build/fable-library") (distDir "fable-library") + // Copy fable-metadata + copyDirRecursive ("src/fable-metadata/lib") (distDir "fable-metadata") + +let testStandalone(minify) = + let fableDir = "src/fable-compiler-js" + let buildDir = "build/tests/Standalone" + + if not (pathExists "build/fable-compiler-js") then + buildCompilerJs(minify) + + cleanDirs [buildDir] + + // Link fable-compiler-js to local packages + runInDir fableDir "npm link ../fable-metadata" + runInDir fableDir "npm link ../fable-standalone" + + // Test fable-compiler-js locally + run $"node {fableDir} tests/Js/Main/Fable.Tests.fsproj {buildDir}" + runMocha buildDir + + // // Another local fable-compiler-js test + // runInDir (fableDir "test") "node .. test_script.fsx" + // runInDir (fableDir "test") "node test_script.fsx.js" + + // Unlink local packages after test + runInDir fableDir "npm unlink ../fable-metadata && cd ../fable-metadata && npm unlink" + runInDir fableDir "npm unlink ../fable-standalone && cd ../fable-standalone && npm unlink" + +let testReact() = + runFableWithArgs "tests/React" ["--noCache"] + runInDir "tests/React" "npm i && npm test" + +let compileAndRunTestsWithMocha clean projectName = + let projectDir = "tests/Js/" + projectName + let buildDir = "build/tests/Js/" + projectName + + if clean then + cleanDirs [buildDir] + + runFableWithArgs projectDir [ + "--outDir " + buildDir + "--exclude Fable.Core" + ] + + runMocha buildDir + +let testProjectConfigs() = + [ "tests/Integration/ProjectConfigs/DebugWithExtraDefines", "Debug" + "tests/Integration/ProjectConfigs/CustomConfiguration", "Test" + "tests/Integration/ProjectConfigs/ReleaseNoExtraDefines", String.Empty + "tests/Integration/ProjectConfigs/ConsoleApp", String.Empty + ] + |> List.iter (fun (projectDir, configuration) -> + let buildDir = "build/"+ projectDir + + cleanDirs [ buildDir ] + runFableWithArgs projectDir [ + "--outDir " + buildDir + "--exclude Fable.Core" + if not(String.IsNullOrEmpty configuration) then + "--configuration " + configuration + ] + + runMocha buildDir + ) + +let testIntegration() = + runInDir "tests/Integration/Integration" "dotnet run -c Release" + + buildLibraryTsIfNotExists() + runInDir "tests/Integration/Compiler" "dotnet run -c Release" + testProjectConfigs() + +let testJs() = + buildLibraryTsIfNotExists() + + compileAndRunTestsWithMocha true "Main" + + runInDir "tests/Js/Main" "dotnet run -c Release" + + // Adaptive tests must go in a different project to avoid conflicts with Queue shim, see #2559 + compileAndRunTestsWithMocha true "Adaptive" + + testReact() + + if envVarOrNone "CI" |> Option.isSome then + testStandaloneFast() + +let testTypeScript isWatch = + buildLibraryTsIfNotExists() + + let projectDir = "tests/TypeScript" + let buildDir = "build/tests/TypeScript" + let buildDir2 = "build/tests/TypeScriptCompiled" + + cleanDirs [buildDir; buildDir2] + + copyFile (projectDir "tsconfig.json") (buildDir "tsconfig.json") + + runFableWithArgsInDirAs (not isWatch) "." [ + projectDir + "--lang ts" + "--outDir " + buildDir + "--exclude Fable.Core" + if isWatch then + "--watch" + $"--runWatch npm run test-ts" + ] + + runNpmScript "test-ts" [] + +let testPython() = + buildLibraryPyIfNotExists() // NOTE: fable-library-py needs to be built separately. + + let projectDir = "tests/Python" + let buildDir = "build/tests/Python" + + cleanDirs [buildDir] + runInDir projectDir "dotnet test -c Release" + runFableWithArgs projectDir [ + "--outDir " + buildDir + "--exclude Fable.Core" + "--lang Python" + ] + + runInDir buildDir "poetry run pytest -x" + // Testing in Windows + // runInDir buildDir "python -m pytest -x" + +type RustTestMode = + | NoStd + | Default + | Threaded + +let testRust testMode = + // buildLibraryRustIfNotExists() + buildLibraryRust() + + let testAstDir = "src/Fable.Transforms/Rust/AST/Tests" + let projectDir = "tests/Rust" + let buildDir = "build/tests/Rust" + + // limited cleanup to reduce IO churn, speed up rebuilds, + // and save the ssd (target folder can get huge) + cleanDirs [buildDir "src"] + cleanDirs [buildDir "tests"] + cleanDirs [buildDir ".fable"] + + // copy rust only tests files (these must be present when running dotnet test as import expr tests for file presence) + makeDirRecursive (buildDir "tests" "src") + copyFiles (projectDir "tests/src") "*.rs" (buildDir "tests/src") + + // run .NET tests + runInDir testAstDir "dotnet test -c Release" + runInDir projectDir "dotnet test -c Release" + + // build Fable Rust tests + runFableWithArgs projectDir [ + "--outDir " + buildDir + "--exclude Fable.Core" + "--lang Rust" + "--fableLib fable-library-rust" + "--noCache" + if testMode = NoStd then "--define NO_STD_NO_EXCEPTIONS" + ] + + // copy project file + copyFile (projectDir "Cargo.toml") buildDir + + // rustfmt all tests + runInDir buildDir "cargo fmt" + // runInDir buildDir "cargo fix --allow-no-vcs" + runInDir buildDir "cargo build" + + // run Fable Rust tests + match testMode with + | Default -> + runInDir buildDir "cargo test" + | NoStd -> + runInDir buildDir "cargo test --features no_std" + | Threaded -> + runInDir buildDir "cargo test --features threaded" + +let testDart isWatch = + if not (pathExists "build/fable-library-dart") then + buildLibraryDart(true) + + let buildDir = resolveDir "build/tests/Dart" + let sourceDir = resolveDir "tests/Dart" + + cleanDirs [buildDir] + makeDirRecursive buildDir + copyFiles sourceDir "pubspec.*" buildDir + copyFiles sourceDir "analysis_options.yaml" buildDir + copyFiles sourceDir "*.dart" buildDir + + runFableWithArgsInDirAs (not isWatch) sourceDir [ + "src" + "--outDir " + (buildDir "src") + "--exclude Fable.Core" + "--lang Dart" + "--noCache" + if isWatch then + "--watch" + $"--runWatch dart test {buildDir}/main.dart" + ] + runInDir buildDir "dart test main.dart" + +let buildLocalPackageWith pkgDir pkgCommand fsproj action = + let version = Publish.loadReleaseVersion "src/Fable.Cli" + "-local-build-" + DateTime.Now.ToString("yyyyMMdd-HHmm") + action version + updatePkgVersionInFsproj fsproj version + run $"dotnet pack {fsproj} -p:Pack=true -c Release -o {pkgDir}" + + // Return install command + $"""dotnet {pkgCommand} --version "{version}" --add-source {fullPath pkgDir}""" + +let buildLocalPackage pkgDir = + buildLocalPackageWith pkgDir + "tool install fable" + (resolveDir "src/Fable.Cli/Fable.Cli.fsproj") (fun version -> + updateVersionsInFableTransforms version [] |> ignore + buildLibraryTs()) + +let testRepos() = + let repos = [ + "https://github.com/alfonsogarciacaro/FsToolkit.ErrorHandling:update-fable-3", "npm i && npm test" + "https://github.com/fable-compiler/fable-promise:master", "npm i && npm test" + "https://github.com/alfonsogarciacaro/Thoth.Json:nagareyama", "dotnet paket restore && npm i && dotnet fable tests -o tests/bin --run mocha tests/bin" + "https://github.com/alfonsogarciacaro/FSharp.Control.AsyncSeq:nagareyama", "cd tests/fable && npm i && npm test" + "https://github.com/alfonsogarciacaro/Fable.Extras:nagareyama", "dotnet paket restore && npm i && npm test" + "https://github.com/alfonsogarciacaro/Fable.Jester:nagareyama", "npm i && npm test" + "https://github.com/Zaid-Ajaj/Fable.SimpleJson:master", "npm i && npm run test-nagareyama" + ] + + let testDir = tempPath() "fable-repos" + printfn $"Cloning repos to: {testDir}" + + cleanDirs [testDir] + makeDirRecursive testDir + let pkgInstallCmd = buildLocalPackage (testDir "pkg") + + for (repo, command) in repos do + let url, branch = let i = repo.LastIndexOf(":") in repo[..i-1], repo[i+1..] + let name = url[url.LastIndexOf("/") + 1..] + runInDir testDir $"git clone {url} {name}" + let repoDir = testDir name + runInDir repoDir ("git checkout " + branch) + runInDir repoDir "dotnet tool uninstall fable" + runInDir repoDir pkgInstallCmd + runInDir repoDir "dotnet tool restore" + runInDir repoDir command + +let githubRelease() = + match envVarOrNone "GITHUB_USER", envVarOrNone "GITHUB_TOKEN" with + | Some user, Some token -> + try + let version, notes = Publish.loadReleaseVersionAndNotes "src/Fable.Cli" + let notes = notes |> Array.map (fun n -> $"""'{n.Replace("'", @"\'").Replace("`", @"\`")}'""") |> String.concat "," + run $"git commit -am \"Release {version}\" && git push" + runSilent $"""node --eval "require('ghreleases').create({{ user: '{user}', token: '{token}', }}, 'fable-compiler', 'Fable', {{ tag_name: '{version}', name: '{version}', body: [{notes}].join('\n'), }}, (err, res) => {{ if (err != null) {{ console.error(err) }} }})" """ + printfn $"Github release %s{version} created successfully" + with ex -> + printfn $"Github release failed: %s{ex.Message}" + | _ -> failwith "Expecting GITHUB_USER and GITHUB_TOKEN enviromental variables" + +let copyFcsRepo sourceDir = + let targetDir = "src/fcs-fable" + let path1 = "fcs/fcs-fable" + let path2 = "src/Compiler" + cleanDirs [targetDir] + copyDirRecursive (sourceDir path1) targetDir + copyDirRecursive (sourceDir path2) (targetDir path2) + removeFile (targetDir ".gitignore") + let projPath = (targetDir "fcs-fable.fsproj") + let projText = readFile projPath + let projText = + Regex.Replace(projText, + @"(\$\(MSBuildProjectDirectory\)).*?(<\/FSharpSourcesRoot>)", + "$1/src/Compiler$2") + // let projText = + // Regex.Replace(projText, + // @"artifacts\/bin\/FSharp.Core\/Release\/netstandard2.0", + // "lib/fcs") + projText |> writeFile projPath + +let syncFcsRepo() = + // FAKE is giving lots of problems with the dotnet SDK version, ignore it + let cheatWithDotnetSdkVersion dir f = + let path = dir "build.fsx" + let script = readFile path + Regex.Replace(script, @"let dotnetExePath =[\s\S]*DotNetCli\.InstallDotNetSDK", "let dotnetExePath = \"dotnet\" //DotNetCli.InstallDotNetSDK") |> writeFile path + f () + runInDir dir "git reset --hard" + + printfn $"Expecting %s{FCS_REPO} repo to be cloned at %s{FCS_REPO_LOCAL}" + + // TODO: Prompt to reset --hard changes + // service_slim + runInDir FCS_REPO_LOCAL ("git checkout " + FCS_REPO_SERVICE_SLIM_BRANCH) + runInDir FCS_REPO_LOCAL "git pull" + cheatWithDotnetSdkVersion (FCS_REPO_LOCAL "fcs") (fun () -> + runBashOrCmd (FCS_REPO_LOCAL "fcs") "build" "") + copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.dll") "../fable/lib/fcs/" + copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.xml") "../fable/lib/fcs/" + copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.dll") "../fable/lib/fcs/" + copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.xml") "../fable/lib/fcs/" + + // fcs-fable + runInDir FCS_REPO_LOCAL ("git checkout " + FCS_REPO_FABLE_BRANCH) + runInDir FCS_REPO_LOCAL "git pull" + cheatWithDotnetSdkVersion (FCS_REPO_LOCAL "fcs") (fun () -> + runBashOrCmd (FCS_REPO_LOCAL "fcs") "build" "CodeGen.Fable") + copyFcsRepo FCS_REPO_LOCAL + +let packages() = + ["Fable.AST", doNothing + "Fable.Core", doNothing + "Fable.Cli", (fun () -> + // TODO: Add library versions for other languages + let compilerVersion = Publish.loadReleaseVersion "src/Fable.Cli" + let updatedLibs = updateVersionsInFableTransforms compilerVersion [ + "js", getNpmVersion "src/fable-library" + ] + buildLibraryTs() + buildLibraryPy() + buildLibraryRust() + buildLibraryDart true + if updatedLibs.Contains("js") then + pushNpmWithoutReleaseNotesCheck "build/fable-library" + ) + "Fable.PublishUtils", doNothing + "fable-metadata", doNothing + "fable-standalone", fun () -> buildStandalone {|minify=true; watch=false|} + "fable-compiler-js", fun () -> buildCompilerJs true + ] + +let publishPackages restArgs = + let packages = + match List.tryHead restArgs with + | Some pkg -> packages() |> List.filter (fun (name,_) -> name = pkg) + | None -> packages() + for (pkg, buildAction) in packages do + if Char.IsUpper pkg[0] then + let projFile = "src" pkg pkg + ".fsproj" + pushFableNuget projFile ["Pack", "true"] buildAction + else + pushNpm ("src" pkg) buildAction + +let hasFlag flag = + BUILD_ARGS_LOWER |> List.contains flag + +match BUILD_ARGS_LOWER with +// | "check-sourcemaps"::_ -> +// ("src/quicktest/Quicktest.fs", "src/quicktest/bin/Quicktest.js", "src/quicktest/bin/Quicktest.js.map") +// |||> sprintf "nodemon --watch src/quicktest/bin/Quicktest.js --exec 'source-map-visualization --sm=\"%s;%s;%s\"'" +// |> List.singleton |> quicktest +// | "coverage"::_ -> coverage() +| ("test"|"test-js")::_ -> testJs() +| "test-mocha"::_ -> compileAndRunTestsWithMocha true "Main" +| "test-mocha-fast"::_ -> compileAndRunTestsWithMocha false "Main" +| "test-react"::_ -> testReact() +| "test-standalone"::_ -> + let minify = hasFlag "--no-minify" |> not + testStandalone(minify) +| "test-standalone-fast"::_ -> testStandaloneFast() +| "test-configs"::_ -> testProjectConfigs() +| "test-integration"::_ -> testIntegration() +| "test-repos"::_ -> testRepos() +| ("test-ts"|"test-typescript")::_ -> testTypeScript(false) +| ("watch-test-ts"|"watch-test-typescript")::_ -> testTypeScript(true) +| "test-py"::_ -> testPython() +| "test-rust"::_ -> testRust Default +| "test-rust-no_std"::_ -> testRust NoStd +| "test-rust-default"::_ -> testRust Default +| "test-rust-threaded"::_ -> testRust Threaded +| "test-dart"::_ -> testDart(false) +| "watch-test-dart"::_ -> testDart(true) + +| "quicktest"::_ -> + buildLibraryTsIfNotExists() + watchFableWithArgs "src/quicktest" ["--watch --exclude Fable.Core --noCache --runScript"] +| "quicktest-ts"::_ -> + buildLibraryTsIfNotExists() + let srcDir = "src/quicktest" + let outPath = "build/quicktest-ts/Quicktest.fs.js" + // Make sure outPath exists so nodemon doesn't complain + if not(pathExists outPath) then + makeDirRecursive (dirname outPath) + writeFile outPath "" + let runCmd = $"npx concurrently \"tsc -w -p {srcDir} --outDir {dirname outPath}\" \"nodemon -w {outPath} {outPath}\"" + watchFableWithArgs srcDir ["--lang ts --watch --exclude Fable.Core --noCache --run"; runCmd] +| ("quicktest-py"|"quicktest-python")::_ -> + buildLibraryPyIfNotExists() + watchFableWithArgs "src/quicktest-py" ["--lang py --watch --exclude Fable.Core --noCache --runScript"] +| "quicktest-dart"::_ -> + buildLibraryDartIfNotExists() + watchFableWithArgs "src/quicktest-dart" ["--lang dart --watch --exclude Fable.Core --noCache --runScript"] +| ("quicktest-rs"|"quicktest-rust")::_ -> + buildLibraryRustIfNotExists() + watchFableWithArgs "src/quicktest-rust" ["--lang rs -e .rs --watch --exclude Fable.Core --noCache --runScript"] +| "run"::_ -> + buildLibraryTsIfNotExists() + // Don't take args from pattern matching because they're lowered + let restArgs = BUILD_ARGS |> List.skip 1 |> String.concat " " + run $"""dotnet run -c Release --project {resolveDir "src/Fable.Cli"} -- {restArgs}""" + +| "package"::_ -> + let pkgInstallCmd = buildLocalPackage (resolveDir "temp/pkg") + printfn $"\nPackage has been created, use the following command to install it:\n {pkgInstallCmd}\n" +| "package-core"::_ -> + let pkgInstallCmd = buildLocalPackageWith (resolveDir "temp/pkg") "add package Fable.Core" (resolveDir "src/Fable.Core/Fable.Core.fsproj") ignore + printfn $"\nFable.Core package has been created, use the following command to install it:\n {pkgInstallCmd}\n" + +| ("fable-library"|"library")::_ +| ("fable-library-ts"|"library-ts")::_ -> buildLibraryTs() +| ("fable-library-py"|"library-py")::_ -> buildLibraryPy() +| ("fable-library-rust" | "library-rust")::_ -> buildLibraryRust() +| ("fable-library-dart" | "library-dart")::_ -> + let clean = hasFlag "--no-clean" |> not + buildLibraryDart(clean) + +| ("fable-compiler-js"|"compiler-js")::_ -> + let minify = hasFlag "--no-minify" |> not + buildCompilerJs(minify) +| ("fable-standalone"|"standalone")::_ -> + let minify = hasFlag "--no-minify" |> not + buildStandalone {|minify=minify; watch=false|} +| ("fable-worker"|"worker")::_ -> + let minify = hasFlag "--no-minify" |> not + buildWorker {|minify=minify; watch=false|} +| "watch-standalone"::_ -> buildStandalone {|minify=false; watch=true|} + +| "sync-fcs-repo"::_ -> syncFcsRepo() +| "copy-fcs-repo"::_ -> copyFcsRepo FCS_REPO_LOCAL + +| "publish"::restArgs -> publishPackages restArgs +| "github-release"::_ -> + publishPackages [] + githubRelease () + +| _ -> + printfn """Please pass a target name. Examples: + +- Use `test` to run tests: + dotnet fsi build.fsx test + +- Use `package` to build a local package: + dotnet fsi build.fsx package + +- Use `run` to compile a project with development version: + dotnet fsi build.fsx run ../path/to/my/project [Fable options] + +- Use `quicktest` to quickly test development version with src/quicktest project: + dotnet fsi build.fsx quicktest +""" + +printfn "Build finished successfully" diff --git a/build.fsx b/build.fsx index 349eb4f238..32920e6a7d 100644 --- a/build.fsx +++ b/build.fsx @@ -1,893 +1,322 @@ -#load "src/Fable.PublishUtils/PublishUtils.fs" - -open System -open System.Text.RegularExpressions -open PublishUtils - -// ncave FCS fork -let FCS_REPO = "https://github.com/ncave/fsharp" -let FCS_REPO_LOCAL = "../fsharp_fable" -let FCS_REPO_FABLE_BRANCH = "fable" -let FCS_REPO_SERVICE_SLIM_BRANCH = "service_slim" - -let BUILD_ARGS = - fsi.CommandLineArgs - |> Array.skip 1 - |> List.ofArray - -let BUILD_ARGS_LOWER = - BUILD_ARGS - |> List.map (fun x -> x.ToLower()) - -module Util = - let cleanDirs dirs = - for dir in dirs do - printfn $"Clean {dir}" - removeDirRecursive dir - - // TODO: move to PublishUtils.fs ? - let copyFiles sourceDir searchPattern destDir = - printfn $"Copy {sourceDir searchPattern} to {destDir}" - for source in IO.Directory.GetFiles(sourceDir, searchPattern) do - let fileName = IO.Path.GetFileName(source) - let target = destDir fileName - IO.File.Copy(source, target, true) - - let resolveDir dir = - __SOURCE_DIRECTORY__ dir - - let updateVersionsInFableTransforms compilerVersion (libraryVersions: (string * string) list) = - let mutable updated = Set.empty - - let replaceVersion (lang: string option) version fileContent = - let prefix = - match lang with - | None -> "" - | Some lang -> $"{lang.ToUpperInvariant()}_LIBRARY_" - - Regex.Replace( - fileContent, - $@"^(\s*)let \[] {prefix}VERSION = ""(.*?)""", - (fun (m: Match) -> - match lang with - | Some lang when m.Groups[2].Value <> version -> - updated <- Set.add lang updated - | _ -> () - m.Groups[1].Value + $"let [] {prefix}VERSION = \"{version}\"" - ), - RegexOptions.Multiline) - - let filePath = "src/Fable.Transforms/Global/Compiler.fs" - readFile filePath - |> replaceVersion None compilerVersion - |> List.foldBack (fun (lang, version) fileContent -> - replaceVersion (Some lang) version fileContent) libraryVersions - |> writeFile filePath - - updated - - let updatePkgVersionInFsproj projFile version = - readFile projFile - |> replaceRegex Publish.NUGET_PACKAGE_VERSION (fun m -> - m.Groups[1].Value + version + m.Groups[3].Value) - |> writeFile projFile - - let runTSLint projectDir = - run ("npm run tslint -- --project " + projectDir) - - let runTypeScript projectDir = - run ("npm run tsc -- --project " + projectDir) - - let runTypeScriptWithArgs projectDir args = - run ("npm run tsc -- --project " + projectDir + " " + String.concat " " args) - - let runFableWithArgs projectDir args = - run ("dotnet run -c Release --project src/Fable.Cli -- " + projectDir + " " + String.concat " " args) - - let watchFableWithArgs projectDir args = - run ("dotnet watch --project src/Fable.Cli run -- " + projectDir + " --cwd ../.. " + String.concat " " args) - - let runFableWithArgsInDirAs release projectDir args = - let cliDir = resolveDir "src/Fable.Cli" - let cliArgs = args |> String.concat " " - let cliCmd = $"""dotnet run {if release then "-c Release" else ""} --project {cliDir} -- {cliArgs}""" - runInDir (resolveDir projectDir) cliCmd - - let runFableWithArgsInDir projectDir args = - runFableWithArgsInDirAs true projectDir args - - let runFableWithArgsAsync projectDir args = - runAsync ("dotnet run -c Release --project src/Fable.Cli -- " + projectDir + " " + String.concat " " args) - - let runNpx command args = - run ("npx " + command + " " + (String.concat " " args)) - - let runNpmScript script args = - run ("npm run " + script + " -- " + (String.concat " " args)) - - let runNpmScriptAsync script args = - runAsync ("npm run " + script + " -- " + (String.concat " " args)) - - let runFable projectDir = - runFableWithArgs projectDir [] - - let runMocha testDir = - runNpmScript "mocha" [$"{testDir} --reporter dot -t 10000"] - -open Util - -module Unused = - let downloadAndExtractTo (url: string) (targetDir: string) = - $"npx download --extract --out %s{targetDir} \"%s{url}\"" |> run - - let coverage() = - // report converter - // https://github.com/danielpalme/ReportGenerator - // dotnet tool install dotnet-reportgenerator-globaltool --tool-path tools - if not (pathExists "./bin/tools/reportgenerator") && not (pathExists "./bin/tools/reportgenerator.exe") then - runInDir "." "dotnet tool install dotnet-reportgenerator-globaltool --tool-path bin/tools" - let reportGen = - if pathExists "./bin/tools/reportgenerator" then "bin/tools/reportgenerator" - else "bin\\tools\\reportgenerator.exe" - - // if not (pathExists "build/fable-library") then - // buildLibrary() - - cleanDirs ["build/tests"] - runFable "tests" - - // JS - run "npx nyc mocha build/tests --require source-map-support/register --reporter dot -t 10000" - runInDir "." (reportGen + " \"-reports:build/coverage/nyc/lcov.info\" -reporttypes:Html \"-targetdir:build/coverage/nyc/html\" ") - - // .NET - //runInDir "tests/Main" "dotnet build /t:Collect_Coverage" - cleanDirs ["build/coverage/netcoreapp2.0/out"] - runInDir "." (reportGen + " \"-reports:build/coverage/netcoreapp2.0/coverage.xml\" -reporttypes:Html \"-targetdir:build/coverage/netcoreapp2.0/html\" ") - -// TARGETS --------------------------- - -// let buildLibraryJsWithOptions (opts: {| watch: bool |}) = -// let baseDir = __SOURCE_DIRECTORY__ - -// let projectDir = baseDir "src/fable-library" -// let buildDir = baseDir "build/fable-library" -// let fableOpts = [ -// "--outDir " + buildDir -// "--fableLib " + buildDir -// "--exclude Fable.Core" -// "--define FX_NO_BIGINT" -// "--define FABLE_LIBRARY" -// if opts.watch then "--watch" -// ] - -// cleanDirs [buildDir] -// runInDir baseDir "npm install" -// makeDirRecursive buildDir - -// copyFile (projectDir "package.json") buildDir - -// if opts.watch then -// Async.Parallel [ -// runNpmScriptAsync "tsc" [ -// "--project " + projectDir -// "--watch" -// ] -// runFableWithArgsAsync projectDir fableOpts -// ] |> runAsyncWorkflow -// else -// runTSLint projectDir -// runTypeScript projectDir -// runFableWithArgs projectDir fableOpts -// removeDirRecursive (buildDir ".fable") - -// let buildLibraryJs() = buildLibraryJsWithOptions {| watch = false |} - -// let buildLibraryJsIfNotExists() = -// if not (pathExists (__SOURCE_DIRECTORY__ "build/fable-library")) then -// buildLibraryJs() - -let buildLibraryTs() = - let baseDir = __SOURCE_DIRECTORY__ - let sourceDir = "./src/fable-library" - let buildDirTs = "./build/fable-library-ts" - let buildDirJs = "./build/fable-library" - - cleanDirs [buildDirTs; buildDirJs] - runInDir baseDir "npm install" - - runFableWithArgs sourceDir [ - "--outDir " + buildDirTs - "--fableLib " + buildDirTs - "--lang TypeScript" - "--typedArrays false" - "--exclude Fable.Core" - "--define FX_NO_BIGINT" - "--define FABLE_LIBRARY" - ] - - copyFiles sourceDir "*.ts" buildDirTs - copyFiles (sourceDir "ts") "*.json" buildDirTs - copyDirRecursive (sourceDir "lib") (buildDirTs "lib") - copyFile (sourceDir "package.json") buildDirTs - - // runTSLint buildDirTs - runTypeScriptWithArgs buildDirTs ["--outDir " + buildDirJs] - copyFile (buildDirTs "lib/big.d.ts") (buildDirJs "lib/big.d.ts") - copyFile (buildDirTs "package.json") buildDirJs - - copyFile (sourceDir "README.md") buildDirJs - -let buildLibraryTsIfNotExists() = - if not (pathExists (__SOURCE_DIRECTORY__ "build/fable-library-ts")) then - buildLibraryTs() - -let buildLibraryPy() = - let libraryDir = "./src/fable-library-py" - let projectDir = libraryDir "fable_library" - let buildDirPy = "./build/fable-library-py" - - cleanDirs [buildDirPy] - - runFableWithArgs projectDir [ - "--outDir " + buildDirPy "fable_library" - "--fableLib " + buildDirPy "fable_library" - "--lang Python" - "--exclude Fable.Core" - "--define FABLE_LIBRARY" - ] - - // Copy python related files from projectDir to buildDir - copyFiles libraryDir "*" buildDirPy - copyFiles projectDir "*.py" (buildDirPy "fable_library") - - // Fix issues with Fable .fsproj not supporting links - copyDirNonRecursive (buildDirPy "fable_library/fable-library") (buildDirPy "fable_library") - removeDirRecursive (buildDirPy "fable_library/fable-library") - -let buildLibraryPyIfNotExists() = - let baseDir = __SOURCE_DIRECTORY__ - if not (pathExists (baseDir "build/fable-library-py")) then - buildLibraryPy() - -let buildLibraryRust() = - let libraryDir = "src/fable-library-rust" - let sourceDir = libraryDir "src" - let buildDir = "build/fable-library-rust" - let outDir = buildDir "src" - let fableLib = "." - - cleanDirs [buildDir] - - runFableWithArgsInDir sourceDir [ - "--outDir " + resolveDir outDir - "--fableLib " + fableLib - "--lang Rust" - "--exclude Fable.Core" - "--define FABLE_LIBRARY" - "--noCache" - ] - - copyFiles libraryDir "*.toml" buildDir - copyFiles sourceDir "*.rs" outDir - copyDirRecursive (libraryDir "vendored") (buildDir "vendored") - - runInDir buildDir "cargo fmt" - runInDir buildDir "cargo fix --allow-no-vcs" - runInDir buildDir "cargo build" - -let buildLibraryRustIfNotExists() = - if not (pathExists (__SOURCE_DIRECTORY__ "build/fable-library-rust")) then - buildLibraryRust() - -let buildLibraryDart(clean: bool) = - let sourceDir = resolveDir "src/fable-library-dart" - let buildDir = resolveDir "build/fable-library-dart" - - if clean then - cleanDirs [buildDir] - makeDirRecursive buildDir - copyFiles sourceDir "pubspec.*" buildDir - copyFiles sourceDir "analysis_options.yaml" buildDir - - copyFiles sourceDir "*.dart" buildDir - - runFableWithArgsInDir sourceDir [ - "--outDir " + buildDir - "--fableLib " + buildDir - "--lang Dart" - "--exclude Fable.Core" - "--define FABLE_LIBRARY" - if clean then "--noCache" - ] - -let buildLibraryDartIfNotExists() = - if not (pathExists (__SOURCE_DIRECTORY__ "build/fable-library-dart")) then - buildLibraryDart(true) - -// Like testStandalone() but doesn't create bundles/packages for fable-standalone & friends -// Mainly intended for CI -let testStandaloneFast() = - runFableWithArgs "src/fable-standalone/src" [ - "--noCache" - ] - - runFableWithArgs "src/fable-compiler-js/src" [ - "--exclude Fable.Core" - "--define LOCAL_TEST" - ] - - let fableJs = "./src/fable-compiler-js/src/app.fs.js" - let testProj = "tests/Js/Main/Fable.Tests.fsproj" - let buildDir = "build/tests/Standalone" - run $"node {fableJs} {testProj} {buildDir}" - runMocha buildDir - -let buildWorker (opts: {| minify: bool; watch: bool |}) = - printfn "Building worker%s..." (if opts.minify then "" else " (no minification)") - - let projectDir = "src/fable-standalone/src" - let buildDir = "build/fable-standalone" - let distDir = "src/fable-standalone/dist" - - runFableWithArgs (projectDir + "/Worker") [ - "--outDir " + buildDir + "/worker" - ] - - let rollupTarget = - match opts.minify with - | true -> buildDir "worker.js" - | false -> distDir "worker.min.js" - - // make standalone worker dist - runNpmScript "rollup" [$"""{buildDir}/worker/Worker.js -o {rollupTarget} --format iife"""] - - if opts.minify then - // runNpx "webpack" [sprintf "--entry ./%s/worker.js --output ./%s/worker.min.js --config ./%s/../worker.config.js" buildDir distDir projectDir] - runNpmScript "terser" [$"{buildDir}/worker.js -o {distDir}/worker.min.js --mangle --compress"] - - // Put fable-library files next to bundle - printfn "Copying fable-library..." - buildLibraryTsIfNotExists() - let libraryDir = "build/fable-library" - let libraryTarget = distDir "fable-library" - copyDirRecursive libraryDir libraryTarget - -let buildStandalone (opts: {| minify: bool; watch: bool |}) = - buildLibraryTs() - - printfn "Building standalone%s..." (if opts.minify then "" else " (no minification)") - - let projectDir = "src/fable-standalone/src" - let buildDir = "build/fable-standalone" - let distDir = "src/fable-standalone/dist" - - let rollupTarget = - match opts.watch, opts.minify with - | true, _ -> - match BUILD_ARGS with - | _::rollupTarget::_ -> rollupTarget - | _ -> failwith "Pass the bundle output, e.g.: npm run build watch-standalone ../repl3/public/js/repl/bundle.min.js" - | false, true -> buildDir "bundle.js" - | false, false -> distDir "bundle.min.js" - - let rollupArgs = [ - buildDir "bundle/Main.js" - "-o " + rollupTarget - "--format umd" - "--name __FABLE_STANDALONE__" - ] - - // cleanup - if not opts.watch then - cleanDirs [buildDir; distDir] - makeDirRecursive distDir - - // build standalone bundle - runFableWithArgs projectDir [ - "--outDir " + buildDir "bundle" - if opts.watch then - "--watch" - "--run rollup" - yield! rollupArgs - "--watch" - ] - - // make standalone bundle dist - runNpmScript "rollup" rollupArgs - if opts.minify then - runNpmScript "terser" [ - buildDir "bundle.js" - "-o " + distDir "bundle.min.js" - "--mangle" - "--compress" - ] - - // build standalone worker - buildWorker opts - - // print bundle size - fileSizeInBytes (distDir "bundle.min.js") / 1000. |> printfn "Bundle size: %fKB" - fileSizeInBytes (distDir "worker.min.js") / 1000. |> printfn "Worker size: %fKB" - -let buildCompilerJs(minify: bool) = - let projectDir = "src/fable-compiler-js/src" - let buildDir = "build/fable-compiler-js" - let distDir = "src/fable-compiler-js/dist" - - if not (pathExists "build/fable-standalone") then - buildStandalone {|minify=minify; watch=false|} - - cleanDirs [buildDir; distDir] - makeDirRecursive distDir - - runFableWithArgs projectDir [ - "--outDir " + buildDir - "--exclude Fable.Core" - ] - - let rollupTarget = if minify then distDir "app.js" else distDir "app.min.js" - run $"npx rollup {buildDir}/app.js -o {rollupTarget} --format umd --name Fable" - if minify then - run $"npx terser {distDir}/app.js -o {distDir}/app.min.js --mangle --compress" - - // Copy fable-library - copyDirRecursive ("build/fable-library") (distDir "fable-library") - // Copy fable-metadata - copyDirRecursive ("src/fable-metadata/lib") (distDir "fable-metadata") - -let testStandalone(minify) = - let fableDir = "src/fable-compiler-js" - let buildDir = "build/tests/Standalone" - - if not (pathExists "build/fable-compiler-js") then - buildCompilerJs(minify) - - cleanDirs [buildDir] - - // Link fable-compiler-js to local packages - runInDir fableDir "npm link ../fable-metadata" - runInDir fableDir "npm link ../fable-standalone" - - // Test fable-compiler-js locally - run $"node {fableDir} tests/Js/Main/Fable.Tests.fsproj {buildDir}" - runMocha buildDir - - // // Another local fable-compiler-js test - // runInDir (fableDir "test") "node .. test_script.fsx" - // runInDir (fableDir "test") "node test_script.fsx.js" - - // Unlink local packages after test - runInDir fableDir "npm unlink ../fable-metadata && cd ../fable-metadata && npm unlink" - runInDir fableDir "npm unlink ../fable-standalone && cd ../fable-standalone && npm unlink" - -let testReact() = - runFableWithArgs "tests/React" ["--noCache"] - runInDir "tests/React" "npm i && npm test" - -let compileAndRunTestsWithMocha clean projectName = - let projectDir = "tests/Js/" + projectName - let buildDir = "build/tests/Js/" + projectName - - if clean then - cleanDirs [buildDir] - - runFableWithArgs projectDir [ - "--outDir " + buildDir - "--exclude Fable.Core" - ] - - runMocha buildDir - -let testProjectConfigs() = - [ "tests/Integration/ProjectConfigs/DebugWithExtraDefines", "Debug" - "tests/Integration/ProjectConfigs/CustomConfiguration", "Test" - "tests/Integration/ProjectConfigs/ReleaseNoExtraDefines", String.Empty - "tests/Integration/ProjectConfigs/ConsoleApp", String.Empty - ] - |> List.iter (fun (projectDir, configuration) -> - let buildDir = "build/"+ projectDir - - cleanDirs [ buildDir ] - runFableWithArgs projectDir [ - "--outDir " + buildDir - "--exclude Fable.Core" - if not(String.IsNullOrEmpty configuration) then - "--configuration " + configuration - ] - - runMocha buildDir - ) - -let testIntegration() = - runInDir "tests/Integration/Integration" "dotnet run -c Release" +// #r "nuget: Fun.Build, 0.5.1" +#r "nuget: Fake.IO.FileSystem, 5.23.1" +#r "nuget: Fake.Core.Environment, 5.23.1" +#r "nuget: Fake.Tools.Git, 5.23.1" +#r "nuget: Fake.Api.GitHub, 5.23.1" +#r "nuget: BlackFox.CommandLine, 1.0.0" +#r "nuget: FsToolkit.ErrorHandling, 4.9.0" +#r "nuget: Fli, 1.10.1" + +// open Fun.Build +// open System.IO +// open Fake.IO +// open BlackFox.CommandLine +// open FsToolkit.ErrorHandling + +// module Commands = + +// let isWatch = +// CmdArg.Create( +// "-w", +// "--watch", +// "Watch for changes and recompile", +// isOptional = true +// ) + +// module TypeScript = + +// let fableBuildDest = "build/tests/TypeScript" +// let typeScriptBuildDest = "build/tests/TypeScriptCompiled" +// let projectDir = "tests/TypeScript" + +// module Stages = + +// let compileAndTestInWatchMode = +// stage "" { +// whenCmdArg Commands.isWatch + +// stage "Pre-compile" { +// // We need to first pre-compile for TypeScript and Mocha to +// // start in watch mode +// // Could use NPM script, but doing the pre-compilation allow +// // to have the full build system in a single place +// run (fun _ -> +// let args = +// CmdLine.appendRaw projectDir +// >> CmdLine.appendPrefix "--outDir" fableBuildDest +// >> CmdLine.appendPrefix "--lang" "typescript" +// >> CmdLine.appendPrefix "--exclude" "Fable.Core" + +// Cmd.fable args +// ) + +// run (fun _ -> +// let args = +// CmdLine.appendPrefix "-p" fableBuildDest +// >> CmdLine.appendPrefix "--outDir" typeScriptBuildDest + +// Cmd.tsc args +// |> CmdLine.toString +// ) +// } + +// stage "Watch, compile and test" { +// paralle + +// run (fun _ -> +// let args = +// CmdLine.appendRaw projectDir +// >> CmdLine.appendPrefix "--outDir" fableBuildDest +// >> CmdLine.appendPrefix "--lang" "typescript" +// >> CmdLine.appendPrefix "--exclude" "Fable.Core" +// >> CmdLine.appendRaw "--watch" + +// Cmd.fable args +// |> CmdLine.toString +// ) + +// run (fun _ -> +// let args = +// CmdLine.appendPrefix "-p" fableBuildDest +// >> CmdLine.appendPrefix "--outDir" typeScriptBuildDest +// >> CmdLine.appendRaw "--watch" + +// Cmd.tsc args +// |> CmdLine.toString +// ) + +// run (fun _ -> +// // Mocha `--watch` doesn't support for ESM modules +// // So we use nodemon as the watcher +// let args = +// CmdLine.appendPrefix "-e" "js" +// >> CmdLine.appendRaw "--watch" +// >> CmdLine.appendRaw typeScriptBuildDest +// >> CmdLine.appendPrefix "--delay" "200ms" +// >> CmdLine.appendPrefix "--exec" "mocha" +// >> CmdLine.appendRaw $"{typeScriptBuildDest}/{fableBuildDest}" +// >> CmdLine.appendPrefix "--reporter" "dot" +// >> CmdLine.appendPrefix "-t" "10000" + +// Cmd.nodemon args +// |> CmdLine.toString +// ) +// } + +// } + +// let compileAndTest = +// stage "Compile and tests" { +// whenNot { +// cmdArg Commands.isWatch +// } + +// run (fun _ -> +// let args = +// CmdLine.appendRaw projectDir +// >> CmdLine.appendPrefix "--outDir" fableBuildDest +// >> CmdLine.appendPrefix "--lang" "typescript" +// >> CmdLine.appendPrefix "--exclude" "Fable.Core" + +// Cmd.fable args +// |> CmdLine.toString +// ) + +// run (fun _ -> +// let args = +// CmdLine.appendPrefix "-p" fableBuildDest +// >> CmdLine.appendPrefix "--outDir" typeScriptBuildDest + +// Cmd.tsc args +// |> CmdLine.toString +// ) + +// run (fun _ -> +// let args = +// CmdLine.appendRaw $"{typeScriptBuildDest}/{fableBuildDest}" +// >> CmdLine.appendPrefix "--reporter" "dot" +// >> CmdLine.appendPrefix "-t" "10000" + +// Cmd.mocha args +// |> CmdLine.toString +// ) +// } + +// module Stages = + +// module JavaScript = + +// let testWithMocha projectName = +// let outDir = Path.Combine("build", "tests", "Js", projectName) +// let projectDir = Path.Combine("tests", "Js", projectName) + +// stage $"Run test using mocha for project: {projectDir}" { + +// run (fun _ -> +// Shell.cleanDir outDir +// ) + +// run (fun _ -> +// let args = +// CmdLine.appendRaw projectDir +// >> CmdLine.appendPrefix "--outDir" outDir +// >> CmdLine.appendPrefix "--exclude" "Fable.Core" + +// Cmd.fable args +// |> CmdLine.toString +// ) + +// run (fun _ -> +// let args = +// CmdLine.appendRaw outDir +// >> CmdLine.appendPrefix "--reporter" "dot" +// >> CmdLine.appendPrefix "-t" "10000" + +// Cmd.mocha args +// |> CmdLine.toString +// ) +// } + + +// let testStandalone = +// stage "Standalone" { +// whenEnvVar "CI" + +// // Compile Fable standalone to JavaScript +// run (fun _ -> +// let args = +// CmdLine.appendRaw "src/fable-standalone/src/" +// >> CmdLine.appendRaw "--noCache" + +// Cmd.fable args +// |> CmdLine.toString +// ) + +// // Compile Fable compiler to JavaScript +// run (fun _ -> +// let args = +// CmdLine.appendRaw "src/fable-compiler-js/src" +// >> CmdLine.appendPrefix "--exclude" "Fable.Core" +// >> CmdLine.appendPrefix "--define" "LOCAL_TEST" + +// Cmd.fable args +// |> CmdLine.toString +// ) + +// // Compile test project using JavaScript fable compiler +// run (fun _ -> +// let fableJs = "./src/fable-compiler-js/src/app.fs.js" +// let testProj = "tests/Js/Main/Fable.Tests.fsproj" +// let buildDir = "build/tests/Standalone" + +// let args = +// CmdLine.appendRaw fableJs +// >> CmdLine.appendRaw testProj +// >> CmdLine.appendRaw buildDir + +// Cmd.node args +// |> CmdLine.toString +// ) + +// run (fun _ -> +// let args = +// CmdLine.appendRaw "build/tests/Standalone" +// >> CmdLine.appendPrefix "--reporter" "dot" +// >> CmdLine.appendPrefix "-t" "10000" + +// Cmd.mocha args +// |> CmdLine.toString +// ) +// } + - buildLibraryTsIfNotExists() - runInDir "tests/Integration/Compiler" "dotnet run -c Release" - testProjectConfigs() - -let testJs() = - buildLibraryTsIfNotExists() - - compileAndRunTestsWithMocha true "Main" - - runInDir "tests/Js/Main" "dotnet run -c Release" - - // Adaptive tests must go in a different project to avoid conflicts with Queue shim, see #2559 - compileAndRunTestsWithMocha true "Adaptive" - - testReact() - - if envVarOrNone "CI" |> Option.isSome then - testStandaloneFast() - -let testTypeScript isWatch = - buildLibraryTsIfNotExists() - - let projectDir = "tests/TypeScript" - let buildDir = "build/tests/TypeScript" - let buildDir2 = "build/tests/TypeScriptCompiled" - - cleanDirs [buildDir; buildDir2] - - copyFile (projectDir "tsconfig.json") (buildDir "tsconfig.json") - - runFableWithArgsInDirAs (not isWatch) "." [ - projectDir - "--lang ts" - "--outDir " + buildDir - "--exclude Fable.Core" - if isWatch then - "--watch" - $"--runWatch npm run test-ts" - ] - - runNpmScript "test-ts" [] - -let testPython() = - buildLibraryPyIfNotExists() // NOTE: fable-library-py needs to be built separately. - - let projectDir = "tests/Python" - let buildDir = "build/tests/Python" - - cleanDirs [buildDir] - runInDir projectDir "dotnet test -c Release" - runFableWithArgs projectDir [ - "--outDir " + buildDir - "--exclude Fable.Core" - "--lang Python" - ] - - runInDir buildDir "poetry run pytest -x" - // Testing in Windows - // runInDir buildDir "python -m pytest -x" - -type RustTestMode = - | NoStd - | Default - | Threaded - -let testRust testMode = - // buildLibraryRustIfNotExists() - buildLibraryRust() - - let testAstDir = "src/Fable.Transforms/Rust/AST/Tests" - let projectDir = "tests/Rust" - let buildDir = "build/tests/Rust" - - // limited cleanup to reduce IO churn, speed up rebuilds, - // and save the ssd (target folder can get huge) - cleanDirs [buildDir "src"] - cleanDirs [buildDir "tests"] - cleanDirs [buildDir ".fable"] - - // copy rust only tests files (these must be present when running dotnet test as import expr tests for file presence) - makeDirRecursive (buildDir "tests" "src") - copyFiles (projectDir "tests/src") "*.rs" (buildDir "tests/src") - - // run .NET tests - runInDir testAstDir "dotnet test -c Release" - runInDir projectDir "dotnet test -c Release" - - // build Fable Rust tests - runFableWithArgs projectDir [ - "--outDir " + buildDir - "--exclude Fable.Core" - "--lang Rust" - "--fableLib fable-library-rust" - "--noCache" - if testMode = NoStd then "--define NO_STD_NO_EXCEPTIONS" - ] - - // copy project file - copyFile (projectDir "Cargo.toml") buildDir - - // rustfmt all tests - runInDir buildDir "cargo fmt" - // runInDir buildDir "cargo fix --allow-no-vcs" - runInDir buildDir "cargo build" - - // run Fable Rust tests - match testMode with - | Default -> - runInDir buildDir "cargo test" - | NoStd -> - runInDir buildDir "cargo test --features no_std" - | Threaded -> - runInDir buildDir "cargo test --features threaded" - -let testDart isWatch = - if not (pathExists "build/fable-library-dart") then - buildLibraryDart(true) - - let buildDir = resolveDir "build/tests/Dart" - let sourceDir = resolveDir "tests/Dart" - - cleanDirs [buildDir] - makeDirRecursive buildDir - copyFiles sourceDir "pubspec.*" buildDir - copyFiles sourceDir "analysis_options.yaml" buildDir - copyFiles sourceDir "*.dart" buildDir - - runFableWithArgsInDirAs (not isWatch) sourceDir [ - "src" - "--outDir " + (buildDir "src") - "--exclude Fable.Core" - "--lang Dart" - "--noCache" - if isWatch then - "--watch" - $"--runWatch dart test {buildDir}/main.dart" - ] - runInDir buildDir "dart test main.dart" - -let buildLocalPackageWith pkgDir pkgCommand fsproj action = - let version = Publish.loadReleaseVersion "src/Fable.Cli" + "-local-build-" + DateTime.Now.ToString("yyyyMMdd-HHmm") - action version - updatePkgVersionInFsproj fsproj version - run $"dotnet pack {fsproj} -p:Pack=true -c Release -o {pkgDir}" - - // Return install command - $"""dotnet {pkgCommand} --version "{version}" --add-source {fullPath pkgDir}""" - -let buildLocalPackage pkgDir = - buildLocalPackageWith pkgDir - "tool install fable" - (resolveDir "src/Fable.Cli/Fable.Cli.fsproj") (fun version -> - updateVersionsInFableTransforms version [] |> ignore - buildLibraryTs()) - -let testRepos() = - let repos = [ - "https://github.com/alfonsogarciacaro/FsToolkit.ErrorHandling:update-fable-3", "npm i && npm test" - "https://github.com/fable-compiler/fable-promise:master", "npm i && npm test" - "https://github.com/alfonsogarciacaro/Thoth.Json:nagareyama", "dotnet paket restore && npm i && dotnet fable tests -o tests/bin --run mocha tests/bin" - "https://github.com/alfonsogarciacaro/FSharp.Control.AsyncSeq:nagareyama", "cd tests/fable && npm i && npm test" - "https://github.com/alfonsogarciacaro/Fable.Extras:nagareyama", "dotnet paket restore && npm i && npm test" - "https://github.com/alfonsogarciacaro/Fable.Jester:nagareyama", "npm i && npm test" - "https://github.com/Zaid-Ajaj/Fable.SimpleJson:master", "npm i && npm run test-nagareyama" - ] - - let testDir = tempPath() "fable-repos" - printfn $"Cloning repos to: {testDir}" - - cleanDirs [testDir] - makeDirRecursive testDir - let pkgInstallCmd = buildLocalPackage (testDir "pkg") - - for (repo, command) in repos do - let url, branch = let i = repo.LastIndexOf(":") in repo[..i-1], repo[i+1..] - let name = url[url.LastIndexOf("/") + 1..] - runInDir testDir $"git clone {url} {name}" - let repoDir = testDir name - runInDir repoDir ("git checkout " + branch) - runInDir repoDir "dotnet tool uninstall fable" - runInDir repoDir pkgInstallCmd - runInDir repoDir "dotnet tool restore" - runInDir repoDir command - -let githubRelease() = - match envVarOrNone "GITHUB_USER", envVarOrNone "GITHUB_TOKEN" with - | Some user, Some token -> - try - let version, notes = Publish.loadReleaseVersionAndNotes "src/Fable.Cli" - let notes = notes |> Array.map (fun n -> $"""'{n.Replace("'", @"\'").Replace("`", @"\`")}'""") |> String.concat "," - run $"git commit -am \"Release {version}\" && git push" - runSilent $"""node --eval "require('ghreleases').create({{ user: '{user}', token: '{token}', }}, 'fable-compiler', 'Fable', {{ tag_name: '{version}', name: '{version}', body: [{notes}].join('\n'), }}, (err, res) => {{ if (err != null) {{ console.error(err) }} }})" """ - printfn $"Github release %s{version} created successfully" - with ex -> - printfn $"Github release failed: %s{ex.Message}" - | _ -> failwith "Expecting GITHUB_USER and GITHUB_TOKEN enviromental variables" - -let copyFcsRepo sourceDir = - let targetDir = "src/fcs-fable" - let path1 = "fcs/fcs-fable" - let path2 = "src/Compiler" - cleanDirs [targetDir] - copyDirRecursive (sourceDir path1) targetDir - copyDirRecursive (sourceDir path2) (targetDir path2) - removeFile (targetDir ".gitignore") - let projPath = (targetDir "fcs-fable.fsproj") - let projText = readFile projPath - let projText = - Regex.Replace(projText, - @"(\$\(MSBuildProjectDirectory\)).*?(<\/FSharpSourcesRoot>)", - "$1/src/Compiler$2") - // let projText = - // Regex.Replace(projText, - // @"artifacts\/bin\/FSharp.Core\/Release\/netstandard2.0", - // "lib/fcs") - projText |> writeFile projPath - -let syncFcsRepo() = - // FAKE is giving lots of problems with the dotnet SDK version, ignore it - let cheatWithDotnetSdkVersion dir f = - let path = dir "build.fsx" - let script = readFile path - Regex.Replace(script, @"let dotnetExePath =[\s\S]*DotNetCli\.InstallDotNetSDK", "let dotnetExePath = \"dotnet\" //DotNetCli.InstallDotNetSDK") |> writeFile path - f () - runInDir dir "git reset --hard" - - printfn $"Expecting %s{FCS_REPO} repo to be cloned at %s{FCS_REPO_LOCAL}" - - // TODO: Prompt to reset --hard changes - // service_slim - runInDir FCS_REPO_LOCAL ("git checkout " + FCS_REPO_SERVICE_SLIM_BRANCH) - runInDir FCS_REPO_LOCAL "git pull" - cheatWithDotnetSdkVersion (FCS_REPO_LOCAL "fcs") (fun () -> - runBashOrCmd (FCS_REPO_LOCAL "fcs") "build" "") - copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.dll") "../fable/lib/fcs/" - copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.xml") "../fable/lib/fcs/" - copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.dll") "../fable/lib/fcs/" - copyFile (FCS_REPO_LOCAL "artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.xml") "../fable/lib/fcs/" - - // fcs-fable - runInDir FCS_REPO_LOCAL ("git checkout " + FCS_REPO_FABLE_BRANCH) - runInDir FCS_REPO_LOCAL "git pull" - cheatWithDotnetSdkVersion (FCS_REPO_LOCAL "fcs") (fun () -> - runBashOrCmd (FCS_REPO_LOCAL "fcs") "build" "CodeGen.Fable") - copyFcsRepo FCS_REPO_LOCAL - -let packages() = - ["Fable.AST", doNothing - "Fable.Core", doNothing - "Fable.Cli", (fun () -> - // TODO: Add library versions for other languages - let compilerVersion = Publish.loadReleaseVersion "src/Fable.Cli" - let updatedLibs = updateVersionsInFableTransforms compilerVersion [ - "js", getNpmVersion "src/fable-library" - ] - buildLibraryTs() - buildLibraryPy() - buildLibraryRust() - buildLibraryDart true - if updatedLibs.Contains("js") then - pushNpmWithoutReleaseNotesCheck "build/fable-library" - ) - "Fable.PublishUtils", doNothing - "fable-metadata", doNothing - "fable-standalone", fun () -> buildStandalone {|minify=true; watch=false|} - "fable-compiler-js", fun () -> buildCompilerJs true - ] - -let publishPackages restArgs = - let packages = - match List.tryHead restArgs with - | Some pkg -> packages() |> List.filter (fun (name,_) -> name = pkg) - | None -> packages() - for (pkg, buildAction) in packages do - if Char.IsUpper pkg[0] then - let projFile = "src" pkg pkg + ".fsproj" - pushFableNuget projFile ["Pack", "true"] buildAction - else - pushNpm ("src" pkg) buildAction - -let hasFlag flag = - BUILD_ARGS_LOWER |> List.contains flag - -match BUILD_ARGS_LOWER with -// | "check-sourcemaps"::_ -> -// ("src/quicktest/Quicktest.fs", "src/quicktest/bin/Quicktest.js", "src/quicktest/bin/Quicktest.js.map") -// |||> sprintf "nodemon --watch src/quicktest/bin/Quicktest.js --exec 'source-map-visualization --sm=\"%s;%s;%s\"'" -// |> List.singleton |> quicktest -// | "coverage"::_ -> coverage() -| ("test"|"test-js")::_ -> testJs() -| "test-mocha"::_ -> compileAndRunTestsWithMocha true "Main" -| "test-mocha-fast"::_ -> compileAndRunTestsWithMocha false "Main" -| "test-react"::_ -> testReact() -| "test-standalone"::_ -> - let minify = hasFlag "--no-minify" |> not - testStandalone(minify) -| "test-standalone-fast"::_ -> testStandaloneFast() -| "test-configs"::_ -> testProjectConfigs() -| "test-integration"::_ -> testIntegration() -| "test-repos"::_ -> testRepos() -| ("test-ts"|"test-typescript")::_ -> testTypeScript(false) -| ("watch-test-ts"|"watch-test-typescript")::_ -> testTypeScript(true) -| "test-py"::_ -> testPython() -| "test-rust"::_ -> testRust Default -| "test-rust-no_std"::_ -> testRust NoStd -| "test-rust-default"::_ -> testRust Default -| "test-rust-threaded"::_ -> testRust Threaded -| "test-dart"::_ -> testDart(false) -| "watch-test-dart"::_ -> testDart(true) - -| "quicktest"::_ -> - buildLibraryTsIfNotExists() - watchFableWithArgs "src/quicktest" ["--watch --exclude Fable.Core --noCache --runScript"] -| "quicktest-ts"::_ -> - buildLibraryTsIfNotExists() - let srcDir = "src/quicktest" - let outPath = "build/quicktest-ts/Quicktest.fs.js" - // Make sure outPath exists so nodemon doesn't complain - if not(pathExists outPath) then - makeDirRecursive (dirname outPath) - writeFile outPath "" - let runCmd = $"npx concurrently \"tsc -w -p {srcDir} --outDir {dirname outPath}\" \"nodemon -w {outPath} {outPath}\"" - watchFableWithArgs srcDir ["--lang ts --watch --exclude Fable.Core --noCache --run"; runCmd] -| ("quicktest-py"|"quicktest-python")::_ -> - buildLibraryPyIfNotExists() - watchFableWithArgs "src/quicktest-py" ["--lang py --watch --exclude Fable.Core --noCache --runScript"] -| "quicktest-dart"::_ -> - buildLibraryDartIfNotExists() - watchFableWithArgs "src/quicktest-dart" ["--lang dart --watch --exclude Fable.Core --noCache --runScript"] -| ("quicktest-rs"|"quicktest-rust")::_ -> - buildLibraryRustIfNotExists() - watchFableWithArgs "src/quicktest-rust" ["--lang rs -e .rs --watch --exclude Fable.Core --noCache --runScript"] -| "run"::_ -> - buildLibraryTsIfNotExists() - // Don't take args from pattern matching because they're lowered - let restArgs = BUILD_ARGS |> List.skip 1 |> String.concat " " - run $"""dotnet run -c Release --project {resolveDir "src/Fable.Cli"} -- {restArgs}""" - -| "package"::_ -> - let pkgInstallCmd = buildLocalPackage (resolveDir "temp/pkg") - printfn $"\nPackage has been created, use the following command to install it:\n {pkgInstallCmd}\n" -| "package-core"::_ -> - let pkgInstallCmd = buildLocalPackageWith (resolveDir "temp/pkg") "add package Fable.Core" (resolveDir "src/Fable.Core/Fable.Core.fsproj") ignore - printfn $"\nFable.Core package has been created, use the following command to install it:\n {pkgInstallCmd}\n" - -| ("fable-library"|"library")::_ -| ("fable-library-ts"|"library-ts")::_ -> buildLibraryTs() -| ("fable-library-py"|"library-py")::_ -> buildLibraryPy() -| ("fable-library-rust" | "library-rust")::_ -> buildLibraryRust() -| ("fable-library-dart" | "library-dart")::_ -> - let clean = hasFlag "--no-clean" |> not - buildLibraryDart(clean) - -| ("fable-compiler-js"|"compiler-js")::_ -> - let minify = hasFlag "--no-minify" |> not - buildCompilerJs(minify) -| ("fable-standalone"|"standalone")::_ -> - let minify = hasFlag "--no-minify" |> not - buildStandalone {|minify=minify; watch=false|} -| ("fable-worker"|"worker")::_ -> - let minify = hasFlag "--no-minify" |> not - buildWorker {|minify=minify; watch=false|} -| "watch-standalone"::_ -> buildStandalone {|minify=false; watch=true|} - -| "sync-fcs-repo"::_ -> syncFcsRepo() -| "copy-fcs-repo"::_ -> copyFcsRepo FCS_REPO_LOCAL - -| "publish"::restArgs -> publishPackages restArgs -| "github-release"::_ -> - publishPackages [] - githubRelease () - -| _ -> - printfn """Please pass a target name. Examples: - -- Use `test` to run tests: - dotnet fsi build.fsx test - -- Use `package` to build a local package: - dotnet fsi build.fsx package - -- Use `run` to compile a project with development version: - dotnet fsi build.fsx run ../path/to/my/project [Fable options] - -- Use `quicktest` to quickly test development version with src/quicktest project: - dotnet fsi build.fsx quicktest -""" - -printfn "Build finished successfully" +// pipeline "Standalone" { +// // Make the pipeline think it is running on CI +// // This is to activate the condition on the "Standalone" stage +// envVars ["CI", "true"] + +// Stages.testStandalone + +// runIfOnlySpecified +// } + +// type TestsPython () = +// inherit TestsTarget( +// "python", +// BuildFableLibraryPython(), +// Path.Combine("tests", "Python"), +// Path.Combine("build", "tests", "Python") +// ) + +// override this.TestsAgainstTargetStage = +// stage "Run tests against Python" { +// workingDir this.BuildDir + +// run "poetry run pytest -x" +// } + +// type TestsRust() = +// inherit TestsTarget( +// "rust", +// BuildFableLibraryRust(), +// Path.Combine("tests", "Rust"), +// Path.Combine("build", "tests", "Rust") +// ) + +// override this.TestsAgainstTargetStage = +// stage "Run tests against Rust" { +// workingDir this.BuildDir + +// run "cargo test" +// } + +// module Test = + +// let python = TestsPython() + +// pipeline "test-javascript" { +// FableLibrary.javaScript.Stage() + +// // Test against .NET +// stage "Run tests against .NET" { +// workingDir "tests/Js/Main" + +// run "dotnet run -c Release" +// } + +// Stages.JavaScript.testWithMocha "Main" +// Stages.JavaScript.testWithMocha "Adaptive" +// Stages.JavaScript.testReact +// Stages.testStandalone + +// runIfOnlySpecified +// } + +// pipeline "test-typescript" { +// FableLibrary.typeScript.Stage() + +// // Stages below use a flag to determine which stage should +// TypeScript.Stages.compileAndTest +// TypeScript.Stages.compileAndTestInWatchMode + +// runIfOnlySpecified +// } + +// pipeline "test-react" { +// FableLibrary.javaScript.Stage() +// Stages.JavaScript.testReact + +// runIfOnlySpecified +// } + +// // Register the build fable-library pipelines +// FableLibrary.rust.Pipeline() +// FableLibrary.python.Pipeline() +// FableLibrary.dart.Pipeline() +// FableLibrary.typeScript.Pipeline() +// FableLibrary.javaScript.Pipeline() + +// // Register the tests pipelines +// Test.python.Pipeline() + +// tryPrintPipelineCommandHelp() diff --git a/build2/FableLibrary/Core.fs b/build2/FableLibrary/Core.fs new file mode 100644 index 0000000000..2515776d0a --- /dev/null +++ b/build2/FableLibrary/Core.fs @@ -0,0 +1,90 @@ +namespace Build.FableLibrary + +open BlackFox.CommandLine +open Fake.IO +open System.IO +open Build.Utils +open Build.Utils +open System.Diagnostics +open SimpleExec +open Spectre.Console +open SpectreCoff + +/// +/// Building fable-library is similar enough for all the targets +/// that we can use this class to standardise the process. +/// +type BuildFableLibrary + ( + language: string, + libraryDir: string, + sourceDir: string, + buildDir: string, + outDir: string, + ?fableLibArg: string + ) = + + // It seems like the different target have a different way of supporting + // --fableLib argument. + // For example, + // If we provide `--fableLib < out dir path>`, then the Dart target + // generates import './Option.dart' as option; + // Bt if we provides `--fableLib .`, then the Dart target + // generates import '../../src/fable-library-dart/Option.dart' as option; + // Python seems to ignore completely the --fableLib argument. + // Investigation are required to make all the targets behave the same way. + // For now, I am providing a way to override the --fableLib argument in the + // build system but it should be removed once the issue is fixed. + let fableLibArg = defaultArg fableLibArg "." + + member val LibraryDir = libraryDir + member val SourceDir = sourceDir + member val BuildDir = buildDir + member val OutDir = outDir + + // Allow language to be orverriden from "do constructor" + // Useful for JavaScript target which is a specialisation of the TypeScript + member val Language = language with get, set + + abstract member FableArgsBuilder: (CmdLine -> CmdLine) + default _.FableArgsBuilder = id + + abstract member PostFableBuildStage: unit -> unit + default _.PostFableBuildStage() = () + + abstract member CopyStage: unit -> unit + default _.CopyStage() = () + + member this.Run(?skipIfExist : bool) = + let skipIfExist = defaultArg skipIfExist false + + if skipIfExist && Directory.Exists outDir then + Calm "Skipping Fable build stage" |> toConsole + + else + + Calm "Cleaning build directory" |> toConsole + + if Directory.Exists buildDir then + Directory.Delete(buildDir, true) + + Calm "Building Fable.Library" |> toConsole + + let args = + CmdLine.appendRaw sourceDir + >> CmdLine.appendPrefix "--outDir" outDir + >> CmdLine.appendPrefix "--fableLib" fableLibArg + >> CmdLine.appendPrefix "--lang" language + >> CmdLine.appendPrefix "--exclude" "Fable.Core" + >> CmdLine.appendPrefix "--define" "FABLE_LIBRARY" + >> CmdLine.appendRaw "--noCache" + // Target implementation can require additional arguments + >> this.FableArgsBuilder + + Command.Fable(args) + + Calm "Copy stage" |> toConsole + this.CopyStage() + + Calm "Post Fable build stage" |> toConsole + this.PostFableBuildStage() diff --git a/build2/FableLibrary/Dart.fs b/build2/FableLibrary/Dart.fs new file mode 100644 index 0000000000..62635162f8 --- /dev/null +++ b/build2/FableLibrary/Dart.fs @@ -0,0 +1,26 @@ +namespace Build.FableLibrary + +open System.IO +open Fake.IO + +type BuildFableLibraryDart() = + inherit + BuildFableLibrary( + "dart", + Path.Combine("src", "fable-library-dart"), + Path.Combine("src", "fable-library-dart"), + Path.Combine("build", "fable-library-dart"), + Path.Combine("build", "fable-library-dart"), + Path.Combine(".", "build", "fable-library-dart") + ) + + override this.CopyStage() = + Directory.GetFiles(this.SourceDir, "pubspec.*") + |> Shell.copyFiles this.BuildDir + + Shell.copyFile + this.BuildDir + (Path.Combine(this.LibraryDir, "analysis_options.yaml")) + + Directory.GetFiles(this.SourceDir, "*.dart") + |> Shell.copyFiles this.OutDir diff --git a/build2/FableLibrary/JavaScript.fs b/build2/FableLibrary/JavaScript.fs new file mode 100644 index 0000000000..d41de0096a --- /dev/null +++ b/build2/FableLibrary/JavaScript.fs @@ -0,0 +1,42 @@ +namespace Build.FableLibrary + +open System.IO +open Fake.IO +open BlackFox.CommandLine +open SimpleExec + +type BuildFableLibraryJavaScript() = + // JavaScript is a specialisation of the TypeScript target + inherit BuildFableLibraryTypeScript() + + let jsOutDir = Path.Combine("build", "fable-library") + do base.Language <- "javascript" + + override this.PostFableBuildStage() = + // Alias to make it clear which directory is referred to + let tsBuildDir = this.BuildDir + + // Make sure to work with a clean build directory + // We need to delete the directy here because JavaScript is + // a bit special compared to other targets. + // JavaScript things happens after the Fable.Library to TypeScript compilation + if Directory.Exists jsOutDir then + Directory.Delete(jsOutDir, true) + + // Compile the library to JavaScript using the TypeScript compiler + let args = + CmdLine.empty + |> CmdLine.appendRaw "tsc" + |> CmdLine.appendPrefix "--project" tsBuildDir + |> CmdLine.appendPrefix "--outDir" jsOutDir + |> CmdLine.toString + + Command.Run("npx", args) + + // Copy lib/big.d.ts to the JavaScript build directory + let bigDts = Path.Combine(tsBuildDir, "lib", "big.d.ts") + Shell.copyFile bigDts jsOutDir + + Shell.copyFile jsOutDir (Path.Combine(tsBuildDir, "package.json")) + + Shell.copyFile jsOutDir (Path.Combine(this.SourceDir, "README.md")) diff --git a/build2/FableLibrary/Python.fs b/build2/FableLibrary/Python.fs new file mode 100644 index 0000000000..5732fa256d --- /dev/null +++ b/build2/FableLibrary/Python.fs @@ -0,0 +1,32 @@ +namespace Build.FableLibrary + +open System.IO +open Fake.IO +open Build.Utils + +type BuildFableLibraryPython() = + inherit + BuildFableLibrary( + "python", + Path.Combine("src", "fable-library-py"), + Path.Combine("src", "fable-library-py", "fable_library"), + Path.Combine("build", "fable-library-py"), + Path.Combine("build", "fable-library-py", "fable_library") + ) + + override this.CopyStage() = + // // Copy all *.rs files to the build directory + Directory.GetFiles(this.LibraryDir, "*") + |> Shell.copyFiles this.BuildDir + + Directory.GetFiles(this.SourceDir, "*.py") + |> Shell.copyFiles this.OutDir + + override this.PostFableBuildStage() = + // Fix issues with Fable .fsproj not supporting links + let linkedFileFolder = + Path.Combine(this.BuildDir, "fable_library", "fable-library") + + Directory.GetFiles(linkedFileFolder, "*") |> Shell.copyFiles this.OutDir + + Shell.deleteDir (this.BuildDir "fable_library/fable-library") diff --git a/build2/FableLibrary/Rust.fs b/build2/FableLibrary/Rust.fs new file mode 100644 index 0000000000..7aa6d9e303 --- /dev/null +++ b/build2/FableLibrary/Rust.fs @@ -0,0 +1,41 @@ +namespace Build.FableLibrary + +open System.IO +open Fake.IO +open Build.Utils +open SimpleExec + +type BuildFableLibraryRust() = + inherit + BuildFableLibrary( + "rust", + Path.Combine("src", "fable-library-rust"), + Path.Combine("src", "fable-library-rust", "src"), + Path.Combine("build", "fable-library-rust"), + Path.Combine("build", "fable-library-rust", "src") + ) + + override this.PostFableBuildStage() = + Command.Run("cargo", "fmt", workingDirectory = this.BuildDir) + + Command.Run( + "cargo", + "fix --allow-no-vcs", + workingDirectory = this.BuildDir + ) + + Command.Run("cargo", "build", workingDirectory = this.BuildDir) + + override this.CopyStage() = + // Copy all *.rs files to the build directory + Directory.GetFiles(this.SourceDir, "*.rs") + |> Shell.copyFiles this.OutDir + + Shell.copyFile + this.BuildDir + (Path.Combine(this.LibraryDir, "Cargo.toml")) + + Shell.copyDir + (Path.Combine(this.BuildDir, "vendored")) + (Path.Combine(this.LibraryDir, "vendored")) + FileFilter.allFiles diff --git a/build2/FableLibrary/TypeScript.fs b/build2/FableLibrary/TypeScript.fs new file mode 100644 index 0000000000..5d605d6577 --- /dev/null +++ b/build2/FableLibrary/TypeScript.fs @@ -0,0 +1,44 @@ +namespace Build.FableLibrary + +open System.IO +open Fake.IO +open BlackFox.CommandLine + +type BuildFableLibraryTypeScript() = + inherit + BuildFableLibrary( + "typescript", + Path.Combine("src", "fable-library"), + Path.Combine("src", "fable-library"), + Path.Combine("build", "fable-library-ts"), + Path.Combine("build", "fable-library-ts"), + Path.Combine(".", "build", "fable-library-ts") + ) + + override _.FableArgsBuilder = + CmdLine.appendPrefix "--typedArrays" "false" + >> CmdLine.appendPrefix "--define" "FX_NO_BIGINT" + + override this.CopyStage() = + // Copy all *.ts files to the build directory from source directory + Directory.GetFiles(this.SourceDir, "*.ts") + |> Shell.copyFiles this.OutDir + + // Copy the tsconfig.json file to the build directory + let typeScriptConfig = + Path.Combine(this.SourceDir, "ts", "tsconfig.json") + + Shell.copyFile this.OutDir typeScriptConfig + + // Copy the lib folder to the build directory + let libSourceFolder = Path.Combine(this.SourceDir, "lib") + let libDestinationFolder = Path.Combine(this.OutDir, "lib") + Shell.copyDir libDestinationFolder libSourceFolder FileFilter.allFiles + + // Copy the package.json file to the build directory + let packageJson = Path.Combine(this.SourceDir, "package.json") + Shell.copyFile this.OutDir packageJson + + // Copy the README.md file to the build directory + let readme = Path.Combine(this.SourceDir, "README.md") + Shell.copyFile this.OutDir readme diff --git a/build2/Fun.Build.Extensions.fs b/build2/Fun.Build.Extensions.fs new file mode 100644 index 0000000000..c10c9959de --- /dev/null +++ b/build2/Fun.Build.Extensions.fs @@ -0,0 +1,26 @@ +module Fun.Build + +open Fun.Build +open Fun.Build.Internal +open Fun.Build.BuiltinCmdsInternal +open BlackFox.CommandLine + +type StageBuilder with + + /// Add a step to run command. This will not encrypt any sensitive information when print to console. + [] + member inline _.run + ( + [] build: BuildStage, + command: CmdLine + ) = + BuildStage(fun ctx -> + build + .Invoke(ctx) + .AddCommandStep(fun _ -> + async { + let command = command |> CmdLine.toString + return command + } + ) + ) diff --git a/build2/Main.fs b/build2/Main.fs new file mode 100644 index 0000000000..40290322d2 --- /dev/null +++ b/build2/Main.fs @@ -0,0 +1,71 @@ +module Build.Main + +open Build.FableLibrary + +// This is a basic help message, as the CLI parser is not a "real" CLI parser +// For now, it is enough as this is just a dev tool +let printHelp () = + let helpText = + """ +Usage: dotnet run [] + +Available commands: + fable-library + + Options: + --javascript Build fable-library for JavaScript + --typescript Build fable-library for TypeScript + --python Build fable-library for Python + --dart Build fable-library for Dart + --rust Build fable-library for Rust + + tests + Subcommands: + javascript Run the tests for JavaScript + typescript Run the tests for TypeScript + python Run the tests for Python + dart Run the tests for Dart + rust Run the tests for Rust + + Options for all: + --watch + --fast + --no-dotnet When in watch mode, do not run the .NET tests + + Options for JavaScript: + --reat-only Run only the tests for React (can be run in watch mode) + + Options for Rust: + --ast-only Run only the tests for the AST (can be run in watch mode) + --noStd Compile and run the tests without the standard library + --threaded Compile and run the tests with the threaded runtime + """ + + printfn "%s" helpText + +[] +let main argv = + let argv = argv |> Array.map (fun x -> x.ToLower()) |> Array.toList + + match argv with + | "fable-library" :: args -> + match args with + | "--javascript" :: _ -> BuildFableLibraryJavaScript().Run() + | "--typescript" :: _ -> BuildFableLibraryTypeScript().Run() + | "--python" :: _ -> BuildFableLibraryPython().Run() + | "--dart" :: _ -> BuildFableLibraryDart().Run() + | "--rust" :: _ -> BuildFableLibraryRust().Run() + | _ -> printHelp () + | "tests" :: args -> + match args with + | "javascript" :: args -> Tests.JavaScript.handle args + | "typescript" :: args -> Tests.TypeScript.handle args + | "python" :: args -> Tests.Python.handle args + | "dart" :: args -> Tests.Dart.handle args + | "rust" :: args -> Tests.Rust.handle args + | _ -> printHelp () + + | "--help" :: _ + | _ -> printHelp () + + 0 diff --git a/build2/SimpleExec.Extensions.fs b/build2/SimpleExec.Extensions.fs new file mode 100644 index 0000000000..ada8eea499 --- /dev/null +++ b/build2/SimpleExec.Extensions.fs @@ -0,0 +1,152 @@ +module SimpleExec + +open SimpleExec +open BlackFox.CommandLine +open Build.Utils + +type Command with + + static member Fable + ( + args: CmdLine, + ?workingDirectory: string, + ?noEcho, + ?echoPrefix + ) = + let localFableDir = + __SOURCE_DIRECTORY__ ".." "src" "Fable.Cli" + + let args = + CmdLine.concat [ + CmdLine.empty + |> CmdLine.appendRaw "run" + |> CmdLine.appendPrefix "-c" "Release" + |> CmdLine.appendPrefix "--project" localFableDir + |> CmdLine.appendRaw "--" + + args + + ] + |> CmdLine.toString + + Command.Run( + "dotnet", + args, + ?workingDirectory = workingDirectory, + ?noEcho = noEcho, + ?echoPrefix = echoPrefix + ) + + static member Fable + ( + argsBuilder: CmdLine -> CmdLine, + ?workingDirectory: string, + ?noEcho, + ?echoPrefix + ) = + let localFableDir = + __SOURCE_DIRECTORY__ ".." "src" "Fable.Cli" + + let args = + CmdLine.empty + |> CmdLine.appendRaw "run" + |> CmdLine.appendPrefix "-c" "Release" + |> CmdLine.appendPrefix "--project" localFableDir + |> CmdLine.appendRaw "--" + |> argsBuilder + |> CmdLine.toString + + Command.Run( + "dotnet", + args, + ?workingDirectory = workingDirectory, + ?noEcho = noEcho, + ?echoPrefix = echoPrefix + ) + + static member FableAsync + ( + argsBuilder: CmdLine -> CmdLine, + ?workingDirectory, + ?noEcho, + ?echoPrefix + ) = + let localFableDir = + __SOURCE_DIRECTORY__ ".." "src" "Fable.Cli" + + let argsBuilder = + CmdLine.empty + |> CmdLine.appendRaw "run" + |> CmdLine.appendPrefix "-c" "Release" + |> CmdLine.appendPrefix "--project" localFableDir + |> CmdLine.appendRaw "--" + |> argsBuilder + |> CmdLine.toString + + Command.RunAsync( + "dotnet", + argsBuilder, + ?workingDirectory = workingDirectory, + ?noEcho = noEcho, + ?echoPrefix = echoPrefix + ) + + static member WatchFableAsync + ( + argsBuilder: CmdLine -> CmdLine, + ?workingDirectory, + ?noEcho, + ?echoPrefix + ) = + let localFableDir = + __SOURCE_DIRECTORY__ ".." "src" "Fable.Cli" + + let argsBuilder = + CmdLine.empty + |> CmdLine.appendRaw "watch" + |> CmdLine.appendPrefix "--project" localFableDir + |> CmdLine.appendRaw "run" + // Without the release mode, Fable stack overflow when compiling the tests + |> CmdLine.appendPrefix "-c" "Release" + |> CmdLine.appendRaw "--" + |> argsBuilder + |> CmdLine.toString + + Command.RunAsync( + "dotnet", + argsBuilder, + ?workingDirectory = workingDirectory, + ?noEcho = noEcho, + ?echoPrefix = echoPrefix + ) + + static member WatchFableAsync + ( + args: CmdLine, + ?workingDirectory, + ?noEcho, + ?echoPrefix + ) = + let localFableDir = + __SOURCE_DIRECTORY__ ".." "src" "Fable.Cli" + + let args = + CmdLine.concat [ + CmdLine.empty + |> CmdLine.appendRaw "run" + |> CmdLine.appendPrefix "-c" "Release" + |> CmdLine.appendPrefix "--project" localFableDir + |> CmdLine.appendRaw "--" + + args + + ] + |> CmdLine.toString + + Command.RunAsync( + "dotnet", + args, + ?workingDirectory = workingDirectory, + ?noEcho = noEcho, + ?echoPrefix = echoPrefix + ) \ No newline at end of file diff --git a/build2/Tests/Dart.fs b/build2/Tests/Dart.fs new file mode 100644 index 0000000000..a201979f61 --- /dev/null +++ b/build2/Tests/Dart.fs @@ -0,0 +1,75 @@ +module Build.Tests.Dart + +open Build.FableLibrary +open System.IO +open Build.Utils +open BlackFox.CommandLine +open SimpleExec +open Fake.IO + +let private buildDir = Path.Resolve("build", "tests", "Dart") +let private testsFolder = Path.Resolve("tests", "Dart") +let private testsFsprojFolder = Path.Resolve("tests", "Dart", "src") + +let handle (args: string list) = + let skipFableLibrary = args |> List.contains "--fast" + let isWatch = args |> List.contains "--watch" + let noDotnet = args |> List.contains "--no-dotnet" + + BuildFableLibraryPython().Run(skipFableLibrary) + + Directory.clean buildDir + + Directory.GetFiles(testsFolder, "pubspec.*") + |> Seq.iter (Shell.copyFile buildDir) + + Shell.copyFile buildDir (testsFolder "analysis_options.yaml") + + Directory.GetFiles(testsFolder, "*.dart") + |> Seq.iter (Shell.copyFile buildDir) + + let testCmd = $"dart test {buildDir}/main.dart" + + let fableArgs = + CmdLine.concat [ + CmdLine.empty + |> CmdLine.appendRaw testsFsprojFolder + |> CmdLine.appendPrefix "--outDir" (buildDir "src") + |> CmdLine.appendPrefix "--lang" "dart" + |> CmdLine.appendPrefix "--exclude" "Fable.Core" + |> CmdLine.appendRaw "--noCache" + + if isWatch then + CmdLine.empty + |> CmdLine.appendRaw "--watch" + |> CmdLine.appendRaw "--runWatch" + |> CmdLine.appendRaw testCmd + else + CmdLine.empty + |> CmdLine.appendRaw "--run" + |> CmdLine.appendRaw testCmd + ] + + if isWatch then + Async.Parallel [ + if not noDotnet then + Command.RunAsync( + "dotnet", + "watch test -c Release", + workingDirectory = testsFsprojFolder + ) + |> Async.AwaitTask + + Command.WatchFableAsync(fableArgs, workingDirectory = buildDir) + |> Async.AwaitTask + ] + |> Async.RunSynchronously + |> ignore + else + Command.Run( + "dotnet", + "test -c Release", + workingDirectory = testsFsprojFolder + ) + + Command.Fable(fableArgs, workingDirectory = buildDir) diff --git a/build2/Tests/JavaScript.fs b/build2/Tests/JavaScript.fs new file mode 100644 index 0000000000..7f34ad6393 --- /dev/null +++ b/build2/Tests/JavaScript.fs @@ -0,0 +1,115 @@ +module Build.Tests.JavaScript + +open Build.FableLibrary +open System.IO +open System +open BlackFox.CommandLine +open Build.Utils +open Build +open SimpleExec + +let private testReact (isWatch: bool) = + let workingDirectoy = Path.Resolve("tests", "React") + + Command.Run("npm", "install", workingDirectory = workingDirectoy) + + if isWatch then + Async.Parallel [ + Command.WatchFableAsync( + CmdLine.appendRaw "--noCache", + workingDirectory = workingDirectoy + ) + |> Async.AwaitTask + + Command.RunAsync( + "npx", + "jest --watch", + workingDirectory = workingDirectoy + ) + |> Async.AwaitTask + ] + |> Async.RunSynchronously + |> ignore + else + Command.Fable( + CmdLine.appendRaw "--noCache", + workingDirectory = workingDirectoy + ) + + Command.Run("npx", "jest", workingDirectory = workingDirectoy) + +let private handleMainTests (isWatch: bool) (noDotnet : bool)= + let folderName = "Main" + let sourceDir = Path.Resolve("tests", "Js", folderName) + + let destinationDir = + Path.Resolve("build", "tests", "JavaScript", folderName) + + Directory.clean destinationDir + + let mochaCommand = "npx mocha . --reporter dot -t 10000" + + let fableArgs = + CmdLine.concat [ + CmdLine.empty + |> CmdLine.appendRaw sourceDir + |> CmdLine.appendPrefix "--outDir" destinationDir + |> CmdLine.appendPrefix "--lang" "javascript" + |> CmdLine.appendPrefix "--exclude" "Fable.Core" + |> CmdLine.appendRaw "--noCache" + + if isWatch then + CmdLine.empty + |> CmdLine.appendRaw "--watch" + |> CmdLine.appendRaw "--runWatch" + |> CmdLine.appendRaw mochaCommand + else + CmdLine.empty + |> CmdLine.appendRaw "--run" + |> CmdLine.appendRaw mochaCommand + ] + + if isWatch then + // In watch mode, we only test the Main tests to not pollute the logs too much + Async.Parallel [ + if not noDotnet then + Command.RunAsync( + "dotnet", + "watch run -c Release", + workingDirectory = Path.Combine("tests", "Js", "Main") + ) + |> Async.AwaitTask + + Command.WatchFableAsync( + fableArgs, + workingDirectory = destinationDir + ) + |> Async.AwaitTask + ] + |> Async.RunSynchronously + |> ignore + else + Command.Run( + "dotnet", + "run -c Release", + workingDirectory = Path.Combine("tests", "Js", "Main") + ) + + // Test the Main tests against JavaScript + Command.Fable(fableArgs, workingDirectory = destinationDir) + + testReact false + + +let handle (args: string list) = + let isReactOnly = args |> List.contains "--react-only" + let skipFableLibrary = args |> List.contains "--fast" + let isWatch = args |> List.contains "--watch" + let noDotnet = args |> List.contains "--no-dotnet" + + BuildFableLibraryJavaScript().Run(skipFableLibrary) + + if isReactOnly then + testReact isWatch + else + handleMainTests isWatch noDotnet \ No newline at end of file diff --git a/build2/Tests/Python.fs b/build2/Tests/Python.fs new file mode 100644 index 0000000000..7eb749df6f --- /dev/null +++ b/build2/Tests/Python.fs @@ -0,0 +1,62 @@ +module Build.Tests.Python + +open Build.FableLibrary +open System.IO +open Build.Utils +open BlackFox.CommandLine +open SimpleExec + +let private buildDir = Path.Resolve("build", "tests", "Python") +let private sourceDir = Path.Resolve("tests", "Python") + +let handle (args: string list) = + let skipFableLibrary = args |> List.contains "--fast" + let isWatch = args |> List.contains "--watch" + let noDotnet = args |> List.contains "--no-dotnet" + + BuildFableLibraryPython().Run(skipFableLibrary) + + Directory.clean buildDir + + let fableArgs = + CmdLine.concat [ + CmdLine.empty + |> CmdLine.appendRaw sourceDir + |> CmdLine.appendPrefix "--outDir" buildDir + |> CmdLine.appendPrefix "--lang" "python" + |> CmdLine.appendPrefix "--exclude" "Fable.Core" + |> CmdLine.appendRaw "--noCache" + + if isWatch then + CmdLine.empty + |> CmdLine.appendRaw "--watch" + |> CmdLine.appendRaw "--runWatch" + |> CmdLine.appendRaw "poetry run pytest -x" + else + CmdLine.empty + |> CmdLine.appendRaw "--run" + |> CmdLine.appendRaw "poetry run pytest -x" + ] + + if isWatch then + Async.Parallel [ + if not noDotnet then + Command.RunAsync( + "dotnet", + "watch test -c Release", + workingDirectory = sourceDir + ) + |> Async.AwaitTask + + Command.WatchFableAsync(fableArgs, workingDirectory = buildDir) + |> Async.AwaitTask + ] + |> Async.RunSynchronously + |> ignore + else + + // Test against .NET + Command.Run("dotnet", "test -c Release", workingDirectory = sourceDir) + + // Test against Python + Command.Fable(fableArgs, workingDirectory = buildDir) diff --git a/build2/Tests/Rust.fs b/build2/Tests/Rust.fs new file mode 100644 index 0000000000..ac522d9004 --- /dev/null +++ b/build2/Tests/Rust.fs @@ -0,0 +1,277 @@ +module Build.Tests.Rust + +open Build.FableLibrary +open System.IO +open Build.Utils +open BlackFox.CommandLine +open Fake.IO +open Fake.IO.Globbing +open Fake.IO.Globbing.Operators +open SimpleExec + +// module private Commands = + +// let watch = +// CmdArg.Create( +// "-w", +// "--watch", +// "Watch for changes and recompile", +// isOptional = true +// ) + +// let noStd = +// CmdArg.Create( +// "-n", +// "--no-std", +// "Do not use the Rust standard library", +// isOptional = true +// ) + +// let threaded = +// CmdArg.Create( +// "-t", +// "--threaded", +// "Use the Rust threaded runtime", +// isOptional = true +// ) + +// let isWatch (ctx: StageContext) = ctx.TryGetCmdArg watch |> Option.isSome + +// module Stages = + +// let testAST = +// let projectDir = +// Path.Resolve("src", "Fable.Transforms", "Rust", "AST", "Tests") + +// stage "Test AST" { +// workingDir projectDir + +// run "dotnet test -c Release" +// } + +// let mainTests = +// let testsDestinationDir = Path.Resolve("build", "tests", "Rust") +// let testsProjectDir = Path.Resolve("tests", "Rust") + +// stage "Main tests" { + +// stage "Clean up" { +// run (fun _ -> +// // limited cleanup to reduce IO churn, speed up rebuilds, +// // and save the ssd (target folder can get huge) +// let cleanUp dirName = +// if +// Directory.Exists(testsDestinationDir dirName) +// then +// Directory.Delete( +// testsDestinationDir dirName, +// true +// ) + +// Directory.CreateDirectory( +// testsDestinationDir dirName +// ) +// |> ignore + +// cleanUp "src" +// cleanUp "tests" +// cleanUp ".fable" +// ) +// } + +// stage "Copy required files" { +// // copy rust only tests files (these must be present when running dotnet test as import expr tests for file presence) +// run (fun _ -> +// Directory.CreateDirectory( +// testsDestinationDir "tests" "src" +// ) +// |> ignore + +// Shell.copyFile +// testsDestinationDir +// (testsProjectDir "cargo.toml") + +// !!(testsProjectDir "tests" "src" "*.rs") +// |> Seq.iter (fun file -> +// let destionation = +// testsDestinationDir "tests" "src" + +// Shell.copyFile destionation file +// ) +// ) +// } + +// stage "Main tests" { +// stage ".NET" { +// workingDir testsProjectDir + +// run "dotnet test -c Release" +// } + +// run ( +// Cmd.fable ( +// CmdLine.appendRaw testsProjectDir +// >> CmdLine.appendPrefix "--outDir" testsDestinationDir +// >> CmdLine.appendPrefix "--exclude" "Fable.Core" +// >> CmdLine.appendPrefix "--lang" "rust" +// >> CmdLine.appendPrefix +// "--fableLib" +// "fable-library-rust" +// >> CmdLine.appendRaw "--noCache" +// // >> CmdLine.appendIf (Commands.isWatch) "--watch" +// // >> CmdLine.appendIf (Commands.noStd) "--noStd" +// // >> CmdLine.appendIf (Commands.threaded) "--threaded" +// ) +// |> CmdLine.toString +// ) + +// stage "Rust" { +// workingDir testsDestinationDir + +// // run "cargo fmt" +// // run "cargo build" +// run "cargo test" +// } + +// } + +// } + +// let registerPipelines () = + +// pipeline $"test-rust" { +// BuildFableLibraryRust().Stage() + +// whenCmdArg Commands.noStd +// whenCmdArg Commands.threaded + +// Stages.testAST +// Stages.mainTests + +// runIfOnlySpecified +// } + +let private testAst isWatch = + + let projectDir = + Path.Resolve("src", "Fable.Transforms", "Rust", "AST", "Tests") + + if isWatch then + Command.RunAsync( + "dotnet", + "watch test -c Release", + workingDirectory = projectDir + ) + |> Async.AwaitTask + |> ignore + else + Command.Run("dotnet", "test -c Release", workingDirectory = projectDir) + +let mainTestsDestinationDir = Path.Resolve("build", "tests", "Rust") +let mainTestsProjectDir = Path.Resolve("tests", "Rust") + +let handle (args: string list) = + let skipFableLibrary = args |> List.contains "--fast" + let isWatch = args |> List.contains "--watch" + let astOnly = args |> List.contains "--ast-only" + let noStd = args |> List.contains "--no-std" + let threaded = args |> List.contains "--threaded" + let noDotnet = args |> List.contains "--no-dotnet" + + if noStd && threaded then + failwith "Cannot use --no-std and --threaded at the same time" + + BuildFableLibraryRust().Run(skipFableLibrary) + + if astOnly then + testAst isWatch + else + // limited cleanup to reduce IO churn, speed up rebuilds, + // and save the ssd (target folder can get huge) + Directory.clean "src" + Directory.clean "tests" + Directory.clean ".fable" + + // copy rust only tests files (these must be present when running dotnet test as import expr tests for file presence) + Directory.CreateDirectory(mainTestsDestinationDir "tests" "src") + |> ignore + + Shell.copyFile + mainTestsDestinationDir + (mainTestsProjectDir "cargo.toml") + + !!(mainTestsProjectDir "tests" "src" "*.rs") + |> Seq.iter (fun file -> + let destionation = mainTestsDestinationDir "tests" "src" + + Shell.copyFile destionation file + ) + + let cargoTestArgs = + if noStd then + "test --features no_std" + elif threaded then + "test --features threaded" + else + "test" + + let fableArgs = + CmdLine.concat [ + CmdLine.empty + |> CmdLine.appendRaw mainTestsProjectDir + |> CmdLine.appendPrefix "--outDir" mainTestsDestinationDir + |> CmdLine.appendPrefix "--lang" "python" + |> CmdLine.appendPrefix "--exclude" "Fable.Core" + |> CmdLine.appendRaw "--noCache" + |> CmdLine.appendPrefixIf noStd "--define" "NO_STD_NO_EXCEPTIONS" + + if isWatch then + CmdLine.empty + |> CmdLine.appendRaw "--watch" + |> CmdLine.appendRaw "--runWatch" + |> CmdLine.appendRaw cargoTestArgs + else + CmdLine.empty + |> CmdLine.appendRaw "--run" + |> CmdLine.appendRaw cargoTestArgs + ] + + if isWatch then + Async.Parallel [ + if not noDotnet then + Command.RunAsync( + "dotnet", + "watch test -c Release", + workingDirectory = mainTestsProjectDir + ) + |> Async.AwaitTask + + Command.WatchFableAsync( + fableArgs, workingDirectory = mainTestsDestinationDir + ) + |> Async.AwaitTask + ] + |> Async.RunSynchronously + |> ignore + else + Command.Run( + "dotnet", + "test -c Release", + workingDirectory = mainTestsProjectDir + ) + + Command.Fable(fableArgs, workingDirectory = mainTestsDestinationDir) + + // Old build system was running cargo fmt and cargo build + // Is it still needed? + // Command.Run( + // "cargo", + // "fmt", + // workingDirectory = mainTestsDestinationDir + // ) + + // Command.Run( + // "cargo", + // "build", + // workingDirectory = mainTestsDestinationDir + // ) diff --git a/build2/Tests/TypeScript.fs b/build2/Tests/TypeScript.fs new file mode 100644 index 0000000000..00fe1ddf91 --- /dev/null +++ b/build2/Tests/TypeScript.fs @@ -0,0 +1,84 @@ +module Build.Tests.TypeScript + +open Build.FableLibrary +open System.IO +open Build.Utils +open BlackFox.CommandLine +open SimpleExec +open Fake.IO + +let private projectDir = Path.Resolve("tests", "TypeScript") +let private fableDest = Path.Resolve("build", "tests", "TypeScript") +let private tscDest = Path.Resolve("build", "tests", "TypeScriptCompiled") + +let handle (args: string list) = + let skipFableLibrary = args |> List.contains "--fast" + let isWatch = args |> List.contains "--watch" + let noDotnet = args |> List.contains "--no-dotnet" + + BuildFableLibraryPython().Run(skipFableLibrary) + + Directory.clean fableDest + Directory.clean tscDest + + Shell.copyFile fableDest (projectDir "tsconfig.json") + + let tscArgs = $"tsc --outDir {tscDest}" + let mochaArgs = "mocha build/tests/TypeScript --reporter dot -t 10000" + + let fableArgs = + CmdLine.concat [ + CmdLine.empty + |> CmdLine.appendRaw projectDir + |> CmdLine.appendPrefix "--outDir" fableDest + |> CmdLine.appendPrefix "--lang" "typescript" + |> CmdLine.appendPrefix "--exclude" "Fable.Core" + |> CmdLine.appendRaw "--noCache" + + // Let Fable handle the TypeScript invocation + if isWatch then + CmdLine.empty + |> CmdLine.appendRaw "--watch" + |> CmdLine.appendRaw "--runWatch" + |> CmdLine.appendRaw $"npx {tscArgs}" + ] + + let nodemonArgs = + CmdLine.empty + |> CmdLine.appendRaw "nodemon" + |> CmdLine.appendPrefix "-e" "js" + |> CmdLine.appendPrefix "--watch" tscDest + // Avoid polluting the logs when a lot of files change at once + |> CmdLine.appendPrefix "--delay" "1s" + |> CmdLine.appendRaw "--exec" + |> CmdLine.appendRaw "\"" + |> CmdLine.appendRaw mochaArgs + |> CmdLine.appendRaw "\"" + |> CmdLine.toString + + if isWatch then + Async.Parallel [ + if not noDotnet then + Command.RunAsync( + "dotnet", + "watch test -c Release", + workingDirectory = projectDir + ) + |> Async.AwaitTask + + Command.WatchFableAsync(fableArgs, workingDirectory = fableDest) + |> Async.AwaitTask + + Command.RunAsync("npx", nodemonArgs, workingDirectory = tscDest) + |> Async.AwaitTask + ] + |> Async.RunSynchronously + |> ignore + else + Command.Run("dotnet", "test -c Release", workingDirectory = projectDir) + + Command.Fable(fableArgs, workingDirectory = fableDest) + + Command.Run("npx", tscArgs, workingDirectory = fableDest) + + Command.Run("npx", mochaArgs, workingDirectory = tscDest) diff --git a/build2/Utils.fs b/build2/Utils.fs new file mode 100644 index 0000000000..7bd8a359be --- /dev/null +++ b/build2/Utils.fs @@ -0,0 +1,120 @@ +namespace Build.Utils + +open BlackFox.CommandLine +open System +open System.IO + +[] +module Operators = + + let () (p1: string) (p2: string) : string = Path.Combine(p1, p2) + +type Path = + + /// + /// Resolve a path relative to the repository root + /// + static member Resolve([] segments: string array) : string = + let paths = Array.concat [ [| __SOURCE_DIRECTORY__; ".." |]; segments ] + + Path.Combine(paths) + +type Cmd = + + /// Build a command line to invoke the local Fable build + /// + /// + /// If true then dotnet watch will be use + /// If false then dotnet run will be use + /// + /// + /// Returns the command line with the arguments to invoke Fable + /// + static member fable + ( + ?argsBuilder: CmdLine -> CmdLine, + ?watchMode: bool + ) : CmdLine = + let argsBuilder = defaultArg argsBuilder id + // Use absolute path so we can invoke the command from anywhere + let localFableDir = + __SOURCE_DIRECTORY__ ".." "src" "Fable.Cli" + + let watchMode = defaultArg watchMode false + + if watchMode then + CmdLine.empty + |> CmdLine.appendRaw "dotnet" + |> CmdLine.appendRaw "watch" + |> CmdLine.appendPrefix "--project" localFableDir + |> CmdLine.appendRaw "run" + // Without the release mode, Fable stack overflow when compiling the tests + |> CmdLine.appendPrefix "-c" "Release" + |> CmdLine.appendRaw "--" + |> argsBuilder + else + CmdLine.empty + |> CmdLine.appendRaw "dotnet" + |> CmdLine.appendRaw "run" + |> CmdLine.appendPrefix "-c" "Release" + |> CmdLine.appendPrefix "--project" localFableDir + |> CmdLine.appendRaw "--" + |> argsBuilder + + static member node(?argsBuilder: CmdLine -> CmdLine) : CmdLine = + let argsBuilder = defaultArg argsBuilder id + + CmdLine.empty |> CmdLine.appendRaw "node" |> argsBuilder + + static member tsc(?argsBuilder: CmdLine -> CmdLine) : CmdLine = + let argsBuilder = defaultArg argsBuilder id + + CmdLine.empty + |> CmdLine.appendRaw "npx" + |> CmdLine.appendRaw "tsc" + |> argsBuilder + + static member mocha(?argsBuilder: CmdLine -> CmdLine) : CmdLine = + let argsBuilder = defaultArg argsBuilder id + + CmdLine.empty + |> CmdLine.appendRaw "npx" + |> CmdLine.appendRaw "mocha" + |> argsBuilder + + static member nodemon(?argsBuilder: CmdLine -> CmdLine) : CmdLine = + let argsBuilder = defaultArg argsBuilder id + + CmdLine.empty + |> CmdLine.appendRaw "npx" + |> CmdLine.appendRaw "nodemon" + |> argsBuilder + + static member dotnet(?argsBuilder: CmdLine -> CmdLine) : CmdLine = + let argsBuilder = defaultArg argsBuilder id + + CmdLine.empty |> CmdLine.appendRaw "dotnet" |> argsBuilder + + static member jest(?argsBuilder: CmdLine -> CmdLine) : CmdLine = + let argsBuilder = defaultArg argsBuilder id + + CmdLine.empty + |> CmdLine.appendRaw "npx" + |> CmdLine.appendRaw "jest" + |> argsBuilder + + static member npx(?argsBuilder: CmdLine -> CmdLine) : CmdLine = + let argsBuilder = defaultArg argsBuilder id + + CmdLine.empty |> CmdLine.appendRaw "npx" |> argsBuilder + +module Directory = + + /// Delete the directory if exist and ensure it exists + /// Name of the directory + /// + let clean (dir: string) : unit = + if Directory.Exists(dir) then + Directory.Delete(dir, true) + + Directory.CreateDirectory(dir) |> ignore \ No newline at end of file diff --git a/src/fable-library-py/fable_library/Fable.Library.csproj b/src/fable-library-py/fable_library/Fable.Library.csproj new file mode 100644 index 0000000000..0ed8766140 --- /dev/null +++ b/src/fable-library-py/fable_library/Fable.Library.csproj @@ -0,0 +1,35 @@ + + + + Library + netstandard2.0 + $(DefineConstants);FABLE_COMPILER + $(DefineConstants);FX_NO_BIGINT + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Js/Main/TailCallTests.fs b/tests/Js/Main/TailCallTests.fs index 825425d44c..be25f1e726 100644 --- a/tests/Js/Main/TailCallTests.fs +++ b/tests/Js/Main/TailCallTests.fs @@ -128,6 +128,10 @@ type Element = let tests = testList "TailCalls" [ + // The tests belows only past in release mode + // Remove the compiler directive when + // https://github.com/fable-compiler/Fable/issues/3522 is fixed + #if RELEASE testCase "Tailcall works in tail position" <| fun () -> Issue3301.simple 100000 1 |> equal 100001 @@ -136,6 +140,7 @@ let tests = testCase "Tailcall works with tuple deconstruction" <| fun () -> Issue3301.tupleDeconstruction 100000 1 |> equal 100001 + #endif testCase "Recursive functions can be tailcall optimized" <| fun () -> factorial1 1 10 |> equal 3628800