├── tests ├── datas │ ├── TestProject │ │ ├── test.txt │ │ ├── Test2.fs │ │ ├── Content.html │ │ ├── TestProject.fsproj │ │ └── Test.fs │ ├── TestLib2 │ │ ├── Library2.fs │ │ ├── Added.fs │ │ ├── Library.fs │ │ └── TestLib2.fsproj │ ├── TestLib1 │ │ ├── Library.fs │ │ └── TestLib1.fsproj │ └── repro-projects │ │ └── src │ │ ├── Masse.Logging │ │ └── Masse.Logging.fsproj │ │ ├── Masse.AWS.SQS │ │ └── Masse.AWS.SQS.fsproj │ │ ├── Masse.AWS.SecretManager │ │ └── Masse.AWS.SecretManager.fsproj │ │ ├── Masse.Event │ │ └── Masse.Event.fsproj │ │ ├── Masse.Swagger │ │ └── Masse.Swagger.fsproj │ │ ├── Masse.Redis │ │ └── Masse.Redis.fsproj │ │ ├── Masse.AWS.S3 │ │ └── Masse.AWS.S3.fsproj │ │ ├── src.sln │ │ ├── Masse.Entity.Decorator │ │ └── Masse.Entity.Decorator.fsproj │ │ ├── Masse.AWS.ECS │ │ └── Masse.AWS.ECS.fsproj │ │ ├── Masse.Sql │ │ └── Masse.Sql.fsproj │ │ ├── ScratchpadNet45 │ │ └── ScratchpadNet45.fsproj │ │ ├── Masse.Telemetry │ │ └── Masse.Telemetry.fsproj │ │ ├── Masse.Catalog.Batch │ │ └── Masse.Catalog.Batch.fsproj │ │ ├── Masse.Image │ │ └── Masse.Image.fsproj │ │ ├── Masse.AWS.DynamoDb │ │ └── Masse.AWS.DynamoDb.fsproj │ │ ├── Masse.Common.JsonNet │ │ └── Masse.Common.JsonNet.fsproj │ │ ├── Masse.Analytics.Sql │ │ └── Masse.Analytics.Sql.fsproj │ │ ├── Masse.Test │ │ └── Masse.Test.fsproj │ │ ├── Masse.AWS.CloudSearch │ │ └── Masse.AWS.CloudSearch.fsproj │ │ ├── Masse.AWS.Ecommerce │ │ └── Masse.AWS.Ecommerce.fsproj │ │ ├── Masse.API.Image │ │ └── Masse.API.Image.fsproj │ │ ├── Masse.Entity │ │ └── Masse.Entity.fsproj │ │ ├── Masse.Social.Journal │ │ └── Masse.Social.Journal.fsproj │ │ ├── Masse.Source │ │ └── Masse.Source │ │ │ └── Masse.Source.fsproj │ │ ├── Masse.Ping │ │ └── Masse.Ping.fsproj │ │ ├── Masse.GraphDb │ │ └── Masse.GraphDb.fsproj │ │ ├── Masse.Source.Walmart │ │ └── Masse.Source.Walmart.fsproj │ │ ├── Masse.Source.Farfetch │ │ └── Masse.Source.Farfetch.fsproj │ │ ├── Masse.Source.Maisonette │ │ └── Masse.Source.Maisonette.fsproj │ │ ├── Masse.Catalog.Ingest │ │ └── Masse.Catalog.Ingest.fsproj │ │ ├── Masse.Catalog.Sql │ │ └── Masse.Catalog.Sql.fsproj │ │ ├── Masse.Product.Journal │ │ └── Masse.Product.Journal.fsproj │ │ ├── Masse.Source.Everlane │ │ └── Masse.Source.Everlane.fsproj │ │ ├── Masse.Source.Glossier │ │ └── Masse.Source.Glossier.fsproj │ │ ├── Masse.Common │ │ └── Masse.Common.fsproj │ │ ├── Masse.Source.Viglink │ │ └── Masse.Source.Viglink.fsproj │ │ ├── Masse.Source.ShopStyle │ │ └── Masse.Source.ShopStyle.fsproj │ │ ├── Masse.Trigger │ │ └── Masse.Trigger.fsproj │ │ ├── Masse.Source.External │ │ └── Masse.Source.External.fsproj │ │ ├── Masse.Source.TheTot │ │ └── Masse.Source.TheTot.fsproj │ │ ├── Masse.Catalog.Search │ │ └── Masse.Catalog.Search.fsproj │ │ ├── Masse.Source.Rakuten │ │ └── Masse.Source.Rakuten.fsproj │ │ ├── Masse.Catalog.Indexer │ │ └── Masse.Catalog.Indexer.fsproj │ │ ├── Masse.Social.Indexer │ │ └── Masse.Social.Indexer.fsproj │ │ ├── Masse.Source.Download │ │ └── Masse.Source.Download.fsproj │ │ ├── Masse.Social.Sql │ │ └── Masse.Social.Sql.fsproj │ │ ├── Masse.Admin.Task │ │ └── Masse.Admin.Task.fsproj │ │ ├── Scratchpad │ │ └── Scratchpad.fsproj │ │ ├── Masse.Social.Search │ │ └── Masse.Social.Search.fsproj │ │ ├── Masse.SiftScience │ │ └── Masse.SiftScience.fsproj │ │ ├── Masse.GraphQl │ │ └── Masse.GraphQl.fsproj │ │ ├── Masse.Catalog.Journal │ │ └── Masse.Catalog.Journal.fsproj │ │ ├── Masse.API │ │ └── Masse.API.fsproj │ │ └── Masse.Admin.Tools │ │ └── Masse.Admin.Tools.fsproj ├── FcsWatch.Tests │ ├── paket.references │ ├── Program.fs │ ├── FcsWatch.Tests.fsproj │ ├── Types.fs │ └── Tests.fs ├── FcsWatch.InteractionTests │ ├── paket.references │ ├── Program.fs │ ├── FcsWatch.InteractionTests.fsproj │ ├── InteractionTests.fs │ └── Types.fs └── FsLive.Porta.Cli.Tests │ └── FsLive.Porta.Cli.Tests.fsproj ├── .paket ├── paket.exe └── paket.targets ├── src ├── FcsWatch.Core │ ├── paket.references │ ├── FcsWatch.Core.fsproj │ ├── Logger.fs │ ├── Compiler.fs │ └── CompilerTmpEmitter.fs ├── fcswatch-cli │ ├── Properties │ │ └── launchSettings.json │ ├── fcswatch-cli.fsproj │ ├── Program.fs │ └── Share.fs ├── FcsWatch.Binary.Helpers │ ├── FcsWatch.Binary.Helpers.csproj │ └── PublicExtensions.cs ├── FcsWatch.Porta.Interpreter │ ├── FcsWatch.Porta.Interpreter.fsproj │ └── CodeModel.fs ├── FcsWatch.Porta │ ├── FcsWatch.Porta.fsproj │ ├── Main.fs │ ├── CompilerTmpEmitter.fs │ └── Extensions.fs ├── fslive-cli │ ├── fslive-cli.fsproj │ └── fslive.fs └── FcsWatch.Binary │ ├── FcsWatch.Binary.fsproj │ ├── VsCodeHelper.fs │ ├── Extensions.fs │ ├── Main.fs │ ├── DebuggingServer.fs │ └── AutoReload.fs ├── .appveyor.yml ├── paket.dependencies ├── .vscode ├── tasks.json ├── settings.json └── launch.json ├── .circleci └── config.yml ├── LICENSE ├── RELEASE_NOTES.md ├── .gitignore ├── README.md └── FCSWatch.FPublisher.sln /tests/datas/TestProject/test.txt: -------------------------------------------------------------------------------- 1 | 2 | FCSWatch from embedded resource! -------------------------------------------------------------------------------- /.paket/paket.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/humhei/FCSWatch/HEAD/.paket/paket.exe -------------------------------------------------------------------------------- /tests/datas/TestLib2/Library2.fs: -------------------------------------------------------------------------------- 1 | module Library2 2 | let test = " " -------------------------------------------------------------------------------- /tests/datas/TestProject/Test2.fs: -------------------------------------------------------------------------------- 1 | module FS 2 | let ss = "pda d 99sa " -------------------------------------------------------------------------------- /tests/FcsWatch.Tests/paket.references: -------------------------------------------------------------------------------- 1 | Expecto 2 | Dotnet.ProjInfo 3 | Fake.DotNet.Cli 4 | FSharp.Compiler.Service -------------------------------------------------------------------------------- /tests/datas/TestProject/Content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/FcsWatch.InteractionTests/paket.references: -------------------------------------------------------------------------------- 1 | Expecto 2 | Dotnet.ProjInfo 3 | Fake.DotNet.Cli 4 | FSharp.Compiler.Service -------------------------------------------------------------------------------- /src/FcsWatch.Core/paket.references: -------------------------------------------------------------------------------- 1 | Dotnet.ProjInfo 2 | Fake.DotNet.Cli 3 | FSharp.Compiler.Service 4 | Dotnet.ProjInfo.Workspace -------------------------------------------------------------------------------- /tests/datas/TestLib2/Added.fs: -------------------------------------------------------------------------------- 1 | namespace TestLib2 2 | 3 | module Added = 4 | let hello2 name = 5 | printfn "Hello %s" name 6 | -------------------------------------------------------------------------------- /tests/datas/TestLib1/Library.fs: -------------------------------------------------------------------------------- 1 | namespace TestLib1 2 | 3 | module Say = 4 | let hello11 name = 5 | printfn "He l %s" name 6 | 7 | -------------------------------------------------------------------------------- /tests/datas/TestLib2/Library.fs: -------------------------------------------------------------------------------- 1 | namespace TestLib2 2 | 3 | module Say = 4 | let hello2 name = 5 | printfn "He %s " name 6 | 7 | let fromLib2 = "das ssss" -------------------------------------------------------------------------------- /src/fcswatch-cli/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "fcswatch-cli": { 4 | "commandName": "Project", 5 | "commandLineArgs": "--project-file \"D:\\VsCode\\Workspace\\FCSWatch\\tests\\datas\\TestProject\\TestProject.fsproj\"" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | 3 | install: 4 | - dotnet tool install --global fpublisher-cli 5 | 6 | build_script: 7 | - cmd: fpublisher --run-ci 8 | 9 | environment: 10 | nuget_api_key: 11 | secure: uxeBhnDDyowaBY1oDNFPo9Yz9wbJUWwAw8BgagEbHtKLQR3uxOADA7dvO+pcANt0 12 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://api.nuget.org/v3/index.json 2 | storage: none 3 | nuget FSharp.Compiler.Service 4 | nuget Expecto 5 | nuget Dotnet.ProjInfo 6 | nuget Fake.DotNet.Cli ~> 5.13.2 7 | nuget Suave 8 | nuget Dotnet.ProjInfo.Workspace 9 | github aspnet/AspNetCore:316fbbe9c42477b6c1d2088b5ef72f8ee3462186 src/Shared/Process/ProcessExtensions.cs 10 | -------------------------------------------------------------------------------- /src/FcsWatch.Binary.Helpers/FcsWatch.Binary.Helpers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/FcsWatch.Binary.Helpers/PublicExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Text; 5 | 6 | namespace FcsWatch.Binary.Helpers 7 | { 8 | public static class PublicExtensions 9 | { 10 | public static void KillTree(this Process process) => Microsoft.Extensions.Internal.ProcessExtensions.KillTree(process); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Logging/Masse.Logging.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/datas/TestLib2/TestLib2.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "emitCompilerTmp", 6 | "command": "curl", 7 | "group": "build", 8 | "args": [ 9 | "--config", 10 | ".fake/fcswatch/port.cache" 11 | ], 12 | "presentation": { 13 | "reveal": "never" 14 | }, 15 | "problemMatcher": [] 16 | }, 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | general: 3 | branches: 4 | ignore: 5 | - gh-pages # list of branches to ignore 6 | 7 | jobs: 8 | build: 9 | docker: 10 | - image: humhei/docker-dotnet-mono 11 | steps: 12 | - checkout 13 | - run: 14 | name: run-tests 15 | command: | 16 | export FrameworkPathOverride=$(dirname $(which mono))/../lib/mono/4.6.2-api/ 17 | export TERM=xterm-256color 18 | fpublisher --run-ci -------------------------------------------------------------------------------- /src/FcsWatch.Porta.Interpreter/FcsWatch.Porta.Interpreter.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.AWS.SQS/Masse.AWS.SQS.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/FcsWatch.InteractionTests/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | 3 | open Expecto.Logging 4 | open Expecto.Tests 5 | open FcsWatch.InteractionTests.InteractionTests 6 | 7 | let testConfig = 8 | { Expecto.Tests.defaultConfig with 9 | parallelWorkers = 1 10 | verbosity = LogLevel.Debug } 11 | 12 | let tests = 13 | testList "All tests" [ 14 | interactionTests 15 | ] 16 | // Create an interactive checker instance 17 | [] 18 | let main argv = runTests testConfig tests 19 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.AWS.SecretManager/Masse.AWS.SecretManager.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Event/Masse.Event.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Swagger/Masse.Swagger.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Redis/Masse.Redis.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.AWS.S3/Masse.AWS.S3.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/src.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Debug|x64 = Debug|x64 10 | Debug|x86 = Debug|x86 11 | Release|Any CPU = Release|Any CPU 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(SolutionProperties) = preSolution 16 | HideSolutionNode = FALSE 17 | EndGlobalSection 18 | EndGlobal 19 | -------------------------------------------------------------------------------- /tests/FcsWatch.Tests/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | 3 | open Expecto.Logging 4 | open Expecto.Tests 5 | open FcsWatch.Tests.Tests 6 | open System 7 | 8 | let testConfig = 9 | { Expecto.Tests.defaultConfig with 10 | parallelWorkers = 1 11 | verbosity = LogLevel.Debug } 12 | 13 | let tests = 14 | testList "All tests" [ 15 | programTests 16 | webhookTests 17 | pluginTests 18 | functionTests 19 | ] 20 | // Create an interactive checker instance 21 | [] 22 | let main argv = 23 | runTests testConfig tests 24 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Entity.Decorator/Masse.Entity.Decorator.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/FcsWatch.Porta/FcsWatch.Porta.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/datas/TestLib1/TestLib1.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0;net462 4 | true 5 | 6 | 7 | 8 | TestLib2.fsproj 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.AWS.ECS/Masse.AWS.ECS.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Sql/Masse.Sql.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/ScratchpadNet45/ScratchpadNet45.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net461 6 | portable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Telemetry/Masse.Telemetry.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/FcsWatch.Tests/FcsWatch.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Catalog.Batch/Masse.Catalog.Batch.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/fcswatch-cli/fcswatch-cli.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6 6 | fcswatch 7 | True 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Image/Masse.Image.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/FcsWatch.InteractionTests/FcsWatch.InteractionTests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.AWS.DynamoDb/Masse.AWS.DynamoDb.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Common.JsonNet/Masse.Common.JsonNet.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | netcoreapp2.1 6 | portable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Analytics.Sql/Masse.Analytics.Sql.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Test/Masse.Test.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.AWS.CloudSearch/Masse.AWS.CloudSearch.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Configure glob patterns for excluding files and folders. 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.DS_Store": true, 7 | "**/.vs": true, 8 | "**/bin": true, 9 | "**/obj": true, 10 | "**/repro-projects": true, 11 | }, 12 | "search.exclude": { 13 | "**/node_modules": true, 14 | "**/bower_components": true, 15 | "**/packages": true, 16 | "**/paket-files": true, 17 | "**/artifacts": true, 18 | "**/bundles.js": true, 19 | "**/bundles.js.map": true, 20 | "**/temp": true, 21 | "**/repro-projects": true, 22 | }, 23 | "files.trimFinalNewlines": true, 24 | "files.trimTrailingWhitespace": true, 25 | "FSharp.disableFailedProjectNotifications": true 26 | } -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.AWS.Ecommerce/Masse.AWS.Ecommerce.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.API.Image/Masse.API.Image.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | MasseAPIImage 7 | true 8 | Lambda 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Entity/Masse.Entity.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Social.Journal/Masse.Social.Journal.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Always 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Source/Masse.Source/Masse.Source.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/fslive-cli/fslive-cli.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | fslive_cli 7 | fslive 8 | True 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | fslive-cli-test 17 | .NET Core Global Tool for FsLive - allows to send files to Fabulous.LiveUpdate and more 18 | Tool;CLI 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Ping/Masse.Ping.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp2.1 5 | MassePing 6 | true 7 | Lambda 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.GraphDb/Masse.GraphDb.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Source.Walmart/Masse.Source.Walmart.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Source.Farfetch/Masse.Source.Farfetch.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Source.Maisonette/Masse.Source.Maisonette.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/datas/TestProject/TestProject.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6; 5 | true 6 | 7 | 8 | 9 | TestLib.fsproj 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Catalog.Ingest/Masse.Catalog.Ingest.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Always 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Catalog.Sql/Masse.Catalog.Sql.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Product.Journal/Masse.Product.Journal.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Always 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/datas/TestProject/Test.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | 3 | open System 4 | open System.Collections.Concurrent 5 | open System.Threading 6 | open TestLib2 7 | open System.IO 8 | 9 | /// https://github.com/humhei/FCSWatch/issues/23 10 | 11 | //let private getEmbeddedStringFromFile (resourceName:string) = 12 | // let assembly = System.Reflection.Assembly.GetExecutingAssembly() 13 | // printfn "Assembly: %A" assembly 14 | // let rns = assembly.GetManifestResourceNames() 15 | // printfn "Resource Names: %A" rns 16 | // let rn = assembly.GetManifestResourceNames() |> Seq.find (fun n -> n.EndsWith resourceName) 17 | // use s = assembly.GetManifestResourceStream rn 18 | // use sr = new StreamReader(s) 19 | // sr.ReadToEnd() 20 | 21 | //let private resource = getEmbeddedStringFromFile "test.txt" 22 | 23 | [] 24 | let main _ = 25 | 26 | //printfn "Hell world from resource %s" resource 27 | 28 | printfn "HEsSSS 9108 985966911 91100099999ASASSASA" 29 | 30 | /// simulate server 31 | Console.ReadLine() 32 | |> ignore 33 | 34 | 0 // return an integer exit code 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 humhei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug FcsWatch.Tests", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/tests/FcsWatch.Tests/bin/Debug/netcoreapp2.2/FcsWatch.Tests.dll", 12 | }, 13 | { 14 | "name": "Debug FcsWatch.InteractionTests", 15 | "type": "coreclr", 16 | "request": "launch", 17 | "program": "${workspaceFolder}/tests/FcsWatch.InteractionTests/bin/Debug/netcoreapp2.2/FcsWatch.InteractionTests.dll", 18 | }, 19 | { 20 | "name": "launch TestProject", 21 | "type": "coreclr", 22 | "request": "launch", 23 | "preLaunchTask": "emitCompilerTmp", 24 | "program": "${workspaceFolder}/tests/datas/TestProject/bin/Debug/netcoreapp2.2/TestProject.dll", 25 | }, 26 | ] 27 | } -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Source.Everlane/Masse.Source.Everlane.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Source.Glossier/Masse.Source.Glossier.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Common/Masse.Common.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | netcoreapp2.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Source.Viglink/Masse.Source.Viglink.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | viglinkCatTree.txt 11 | PreserveNewest 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Source.ShopStyle/Masse.Source.ShopStyle.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | shopstyle_categories.json 11 | PreserveNewest 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/FsLive.Porta.Cli.Tests/FsLive.Porta.Cli.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.2 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Trigger/Masse.Trigger.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | Lambda 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Source.External/Masse.Source.External.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/FcsWatch.Binary/FcsWatch.Binary.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | $(TargetsForTfmSpecificBuildOutput);IncludeProcessExtensions 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Source.TheTot/Masse.Source.TheTot.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Catalog.Search/Masse.Catalog.Search.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | MasseCatalogSearch 5 | Lambda 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/fcswatch-cli/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | module FcsWatch.Cli.Main 3 | open FcsWatch.Binary 4 | open FcsWatch.Cli.Share 5 | open System.Threading.Tasks 6 | 7 | 8 | let splitArgs args = 9 | let arguArgs = 10 | args 11 | |> Array.takeWhile(fun x -> x <> "--") 12 | let additionalArgs = 13 | args 14 | |> Array.skipWhile(fun x -> x <> "--") 15 | |> (fun a -> if Array.tryHead a = Some "--" then Array.tail a else a) 16 | (arguArgs, additionalArgs) 17 | 18 | [] 19 | let main argv = 20 | try 21 | let exited = TaskCompletionSource() 22 | System.Console.CancelKeyPress.Add(fun _ -> 23 | exited.TrySetResult () 24 | |> ignore 25 | ) 26 | 27 | System.Runtime.Loader.AssemblyLoadContext.Default.add_Unloading(fun _ -> 28 | exited.TrySetResult () 29 | |> ignore 30 | ) 31 | let arguArgs, additionalArgs = splitArgs argv 32 | 33 | let results = parser.Parse arguArgs 34 | 35 | let processResult = processParseResults additionalArgs results 36 | 37 | runFcsWatcher exited.Task processResult.Config processResult.ProjectFile 38 | |> Async.RunSynchronously 39 | 40 | 0 41 | 42 | with :? Argu.ArguParseException as e -> 43 | stdout.WriteLine e.Message 44 | 45 | LanguagePrimitives.EnumToValue e.ErrorCode 46 | -------------------------------------------------------------------------------- /src/FcsWatch.Core/FcsWatch.Core.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472;netstandard2.0 5 | 6 | 7 | TRACE 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 6.0.0 24 | 25 | 26 | 6.0.0 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Source.Rakuten/Masse.Source.Rakuten.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Catalog.Indexer/Masse.Catalog.Indexer.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | MasseCatalogIndexer 5 | Lambda 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/FcsWatch.InteractionTests/InteractionTests.fs: -------------------------------------------------------------------------------- 1 | module FcsWatch.InteractionTests.InteractionTests 2 | open Expecto 3 | open Fake.IO 4 | open Fake.IO.FileSystemOperators 5 | 6 | let pass() = Expect.isTrue true "passed" 7 | let fail() = Expect.isTrue false "failed" 8 | 9 | let root = __SOURCE_DIRECTORY__ "../../" 10 | 11 | let datas = Path.getDirectory(__SOURCE_DIRECTORY__) "datas" 12 | 13 | let entryProjDir = datas "TestProject" 14 | 15 | let entryProjPath = entryProjDir "TestProject.fsproj" 16 | 17 | let testProjPath = datas @"TestLib2/TestLib2.fsproj" 18 | 19 | let testSourceFile1 = datas @"TestLib2/Library.fs" 20 | 21 | let testSourceFile2 = datas @"TestLib2/Library2.fs" 22 | 23 | let testSourceFileAdded = datas @"TestLib2/Added.fs" 24 | 25 | let testSourceFile1InTestLib = datas @"TestLib1/Library.fs" 26 | 27 | 28 | let interactionTests = 29 | testList "interaction tests" [ 30 | testCase "manual reload test" <| fun _ -> 31 | FcsWatch.Cli.Main.main [|"--project-file"; entryProjPath;"--logger-level"; "normal"; "--debuggable" |] 32 | |> ignore 33 | 34 | ftestCase "auto reload test" <| fun _ -> 35 | FcsWatch.Cli.Main.main [|"--project-file"; entryProjPath|] 36 | |> ignore 37 | 38 | testCase "auto reload release" <| fun _ -> 39 | FcsWatch.Cli.Main.main [|"--project-file"; entryProjPath; "--configuration"; "release"|] 40 | |> ignore 41 | 42 | testCase "fslive cli test" <| fun _ -> 43 | FsLive.Driver.main [| entryProjPath; "--watch"; "--loggerlevel:2"; "--send"|] 44 | |> ignore 45 | ] -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Social.Indexer/Masse.Social.Indexer.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | MasseSocialIndexer 5 | Lambda 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Source.Download/Masse.Source.Download.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Always 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Social.Sql/Masse.Social.Sql.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Admin.Task/Masse.Admin.Task.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | MasseAdminTask 6 | Lambda 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Scratchpad/Scratchpad.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp2.1 5 | 6 | 7 | 8 | PreserveNewest 9 | 10 | 11 | 12 | PreserveNewest 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Social.Search/Masse.Social.Search.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | MasseSocialSearch 7 | Lambda 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/FcsWatch.Tests/Types.fs: -------------------------------------------------------------------------------- 1 | module FcsWatch.Tests.Types 2 | open System.Xml 3 | 4 | [] 5 | module Fsproj = 6 | let private equalIgnoreCaseAndEdgeSpace (text1: string) (text2: string) = 7 | match text1.Trim().CompareTo (text2.Trim()) with 8 | | 0 -> true 9 | | _ -> false 10 | 11 | let addFileToProject file (projectFile: string) = 12 | let doc = new XmlDocument() 13 | doc.Load(projectFile) 14 | let compiledFiles = doc.GetElementsByTagName "Compile" 15 | let compiledFileList = 16 | [ for compiledFile in compiledFiles do 17 | yield compiledFile 18 | ] 19 | match List.tryFind (fun (compiledFile: XmlNode) -> 20 | equalIgnoreCaseAndEdgeSpace file compiledFile.Attributes.["Include"].Value) compiledFileList with 21 | | Some _ -> () 22 | | None -> 23 | let firstCompiledFile = compiledFiles.[0] 24 | let addedCompiledFile = firstCompiledFile.Clone() 25 | addedCompiledFile.Attributes.["Include"].Value <- file 26 | 27 | firstCompiledFile.ParentNode.AppendChild addedCompiledFile 28 | |> ignore 29 | 30 | doc.Save(projectFile) 31 | let removeFileFromProject file (projectFile: string) = 32 | let doc = new XmlDocument() 33 | doc.Load(projectFile) 34 | 35 | let compiledFiles = doc.GetElementsByTagName "OutputType" 36 | let compiledFileList = 37 | [ for compiledFile in compiledFiles do 38 | yield compiledFile 39 | ] 40 | match List.tryFind (fun (compiledFile: XmlNode) -> 41 | equalIgnoreCaseAndEdgeSpace file compiledFile.Attributes.["Include"].Value) compiledFileList with 42 | | Some xmlNode -> 43 | xmlNode.ParentNode.RemoveChild xmlNode |> ignore 44 | | None -> () 45 | doc.Save(projectFile) 46 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.SiftScience/Masse.SiftScience.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | MasseSiftScience 7 | Lambda 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /tests/FcsWatch.InteractionTests/Types.fs: -------------------------------------------------------------------------------- 1 | module FcsWatch.InteractionTests.Types 2 | open System.Xml 3 | 4 | [] 5 | module Fsproj = 6 | let private equalIgnoreCaseAndEdgeSpace (text1: string) (text2: string) = 7 | match text1.Trim().CompareTo (text2.Trim()) with 8 | | 0 -> true 9 | | _ -> false 10 | 11 | let addFileToProject file (projectFile: string) = 12 | let doc = new XmlDocument() 13 | doc.Load(projectFile) 14 | let compiledFiles = doc.GetElementsByTagName "Compile" 15 | let compiledFileList = 16 | [ for compiledFile in compiledFiles do 17 | yield compiledFile ] 18 | match List.tryFind (fun (compiledFile: XmlNode) -> 19 | equalIgnoreCaseAndEdgeSpace file compiledFile.Attributes.["Include"].Value) compiledFileList with 20 | | Some _ -> () 21 | | None -> 22 | let firstCompiledFile = compiledFiles.[0] 23 | let addedCompiledFile = firstCompiledFile.Clone() 24 | addedCompiledFile.Attributes.["Include"].Value <- file 25 | 26 | firstCompiledFile.ParentNode.AppendChild addedCompiledFile 27 | |> ignore 28 | 29 | doc.Save(projectFile) 30 | let removeFileFromProject file (projectFile: string) = 31 | let doc = new XmlDocument() 32 | doc.Load(projectFile) 33 | let compiledFiles = doc.GetElementsByTagName "Compile" 34 | let compiledFileList = 35 | [ for compiledFile in compiledFiles do 36 | yield compiledFile 37 | ] 38 | match List.tryFind (fun (compiledFile: XmlNode) -> 39 | equalIgnoreCaseAndEdgeSpace file compiledFile.Attributes.["Include"].Value) compiledFileList with 40 | | Some xmlNode -> 41 | xmlNode.ParentNode.RemoveChild xmlNode |> ignore 42 | | None -> () 43 | doc.Save(projectFile) 44 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.GraphQl/Masse.GraphQl.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/FcsWatch.Porta/Main.fs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Fabulous contributors. See LICENSE.md for license. 2 | 3 | // F# PortaCode command processing (e.g. used by Fabulous.Cli) 4 | 5 | [] 6 | module FcsWatch.Porta.Main 7 | 8 | open System 9 | open FSharp.Compiler.SourceCodeServices 10 | open FcsWatch.Core.Compiler 11 | open FcsWatch.Core 12 | open FcsWatch.Core.FcsWatcher 13 | open FcsWatch.Core.Types 14 | open Extensions 15 | open Fake.IO 16 | open System.IO 17 | 18 | let runFcsWatcher (config: PortaConfig) = 19 | logger <- Logger.create config.LoggerLevel 20 | let compiler = 21 | { new ICompiler with 22 | member x.Compile(checker, crackedFsproj) = async { 23 | let! result = CrackedFsproj.check config.UseEditFiles config.LiveCheckOnly checker crackedFsproj 24 | return [|result|] 25 | } 26 | member x.WarmCompile = true 27 | member x.Summary (_, _) = "" 28 | } 29 | 30 | let compilerTmpEmitter = CompilerTmpEmitter.create config 31 | 32 | 33 | let coreConfig: FcsWatch.Core.Config = 34 | { LoggerLevel = config.LoggerLevel 35 | WorkingDir = config.WorkingDir 36 | UseEditFiles = config.UseEditFiles 37 | OtherFlags = config.OtherFlags 38 | Configuration = Configuration.Debug } 39 | 40 | let checker = FSharpChecker.Create(keepAssemblyContents = true) 41 | 42 | let fcsWatcher, _ = fcsWatcherAndCompilerTmpAgent checker compilerTmpEmitter compiler coreConfig config.Fsproj 43 | 44 | if config.Watch then 45 | Console.ReadLine() |> ignore 46 | elif config.Eval then 47 | let makeSourceFileChanges fullPaths = 48 | let makeFileChange fullPath : FileChange = 49 | let fullPath = Path.getFullName fullPath 50 | 51 | { FullPath = fullPath 52 | Name = Path.GetFileName fullPath 53 | Status = FileStatus.Changed } 54 | 55 | FcsWatcherMsg.DetectSourceFileChanges (List.map makeFileChange fullPaths) 56 | 57 | let cache = fcsWatcher.PostAndReply FcsWatcherMsg.GetCache 58 | 59 | fcsWatcher.PostAndReply (makeSourceFileChanges [cache.EntryCrackedFsproj.SourceFiles.[0]]) 60 | 61 | fcsWatcher.PostAndReply FcsWatcherMsg.WaitCompiled 62 | |> ignore 63 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Catalog.Journal/Masse.Catalog.Journal.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp2.1 5 | MasseCatalogJournal 6 | Lambda 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Always 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/FcsWatch.Binary/VsCodeHelper.fs: -------------------------------------------------------------------------------- 1 | namespace FcsWatch.Binary 2 | open Fake.IO 3 | open FcsWatch.Core 4 | open Fake.IO.FileSystemOperators 5 | 6 | 7 | module internal VscodeHelper = 8 | 9 | type Configuration = 10 | { name: string 11 | ``type``: string 12 | request: string 13 | preLaunchTask: obj 14 | program: obj 15 | args: obj 16 | processId: obj 17 | justMyCode: obj 18 | cwd: obj 19 | stopAtEntry: obj 20 | console: obj } 21 | 22 | type Launch = 23 | { version: string 24 | configurations: Configuration list } 25 | 26 | [] 27 | module Launch = 28 | open Newtonsoft.Json 29 | 30 | let read file = 31 | 32 | let jsonTest = File.readAsString file 33 | JsonConvert.DeserializeObject jsonTest 34 | 35 | let write file (launch: Launch) = 36 | let settings = JsonSerializerSettings(NullValueHandling = NullValueHandling.Ignore) 37 | let jsonText = JsonConvert.SerializeObject(launch,Formatting.Indented,settings) 38 | File.writeString false file jsonText 39 | 40 | let writePid configurationName pid (launch: Launch) = 41 | { launch with 42 | configurations = 43 | let prediate configuration = configuration.request = "attach" && configuration.name = configurationName 44 | 45 | launch.configurations 46 | |> List.tryFind prediate 47 | |> function 48 | | Some _ -> () 49 | | None -> 50 | logger.Info "Cannot find attachable configuration named %s in lanuch.json" configurationName 51 | 52 | let newConfigurations = 53 | 54 | launch.configurations 55 | |> List.map (fun configuration -> 56 | if prediate configuration then 57 | {configuration with processId = pid} 58 | else 59 | configuration 60 | ) 61 | 62 | newConfigurations 63 | } 64 | 65 | [] 66 | module File = 67 | let tryFindLaunchJsonUp workingDir = 68 | let makePath root = root ".vscode" "launch.json" 69 | File.tryFindUntilRoot makePath workingDir 70 | 71 | let tryFindRootUpByLaunchJson workingDir = 72 | tryFindLaunchJsonUp workingDir 73 | |> Option.map (fun path -> 74 | path 75 | |> Path.getDirectory 76 | |> Path.getDirectory 77 | ) -------------------------------------------------------------------------------- /src/FcsWatch.Core/Logger.fs: -------------------------------------------------------------------------------- 1 | namespace FcsWatch.Core 2 | open Fake.Core 3 | 4 | 5 | [] 6 | module Logger = 7 | open System 8 | 9 | [] 10 | type Level = 11 | | Minimal 12 | | Normal 13 | | Quiet 14 | | Debug 15 | 16 | type Logger internal (level) = 17 | 18 | let timeStamp (time:DateTime) = time.ToString("yyyy-MM-dd HH:mm:ss.fff") 19 | 20 | let withTimeStamp (f: string -> unit) = 21 | fun message -> 22 | let now = timeStamp DateTime.UtcNow 23 | sprintf "%s %s" now message 24 | |> f 25 | 26 | 27 | let _debug trace message = 28 | match level with 29 | | Level.Minimal -> () 30 | | Level.Normal -> () 31 | | Level.Quiet -> () 32 | | Level.Debug -> trace message 33 | 34 | 35 | let _info trace message = 36 | match level with 37 | | Level.Quiet -> () 38 | | Level.Minimal -> () 39 | | Level.Normal -> trace message 40 | | Level.Debug -> trace message 41 | 42 | let _important trace message = 43 | match level with 44 | | Level.Quiet -> () 45 | | _ -> trace message 46 | 47 | let _warn message = Trace.traceImportant message 48 | 49 | let _error message = Trace.traceError message 50 | 51 | member x.Info format = 52 | Printf.ksprintf (_info Trace.log) format 53 | 54 | member x.Debug format = 55 | Printf.ksprintf (_debug Trace.log) format 56 | 57 | member x.InfoGreen format = 58 | Printf.ksprintf (_info Trace.trace) format 59 | 60 | member x.Diagnostics text = 61 | System.Diagnostics.Debugger.Log(1,"",sprintf "%s %s\n" (timeStamp DateTime.UtcNow) text) 62 | 63 | /// with timeStamp 64 | member x.Infots format = 65 | Printf.ksprintf (withTimeStamp (_info Trace.log)) format 66 | 67 | member x.InfoGreents format = 68 | Printf.ksprintf (withTimeStamp (_info Trace.trace)) format 69 | 70 | /// LG = light gray 71 | member x.ImportantLG format = 72 | Printf.ksprintf (_important Trace.log) format 73 | 74 | member x.Important format = Printf.ksprintf (_important (printfn "%s")) format 75 | 76 | member x.ImportantGreen format = 77 | Printf.ksprintf (_important Trace.trace) format 78 | 79 | member x.Importantts format = 80 | Printf.ksprintf (withTimeStamp (_important Trace.log)) format 81 | 82 | member x.ImportantGreents format = 83 | Printf.ksprintf (withTimeStamp (_important Trace.trace)) format 84 | 85 | member x.Warn format = 86 | Printf.ksprintf _warn format 87 | 88 | member x.Error format = 89 | Printf.ksprintf _error format 90 | 91 | let create level = Logger(level) 92 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | ## 0.7.15-alpha - tbd 4 | 5 | ## 0.7.14 - 2019-07-26 6 | * using CTRL + C exit fcswatch-cli 7 | 8 | ## 0.7.13 - 2019-6-14 9 | * Support watching additional content files #40 @ @tylerhartwig 10 | 11 | ## 0.7.12 - 2019-6-14 12 | * Support watching additional content files #40 @ @tylerhartwig 13 | 14 | ## 0.7.11 - 2019-05-28 15 | * PORTA: Fixes missing RPrim_float32 #39 @7sharp9 16 | 17 | ## 0.7.10 - 2019-05-15 18 | * Add support for Bolero (detect "blazor serve" and copy dll to dist) #36 19 | 20 | ## 0.7.9 - 2019-04-29 21 | * Map project Other options --doc:path to --doc:fullPath #30 #31 22 | * Monitor error infos in MailBoxProcesser #16 #31 23 | 24 | ## 0.7.8 - 2019-04-27 25 | * Some as 0.7.6 26 | 27 | ## 0.7.7 - 2019-04-27 28 | * Some as 0.7.6 29 | 30 | ## 0.7.6 - 2019-04-27 31 | * Added `--configuration release` and `--framework netframework` 32 | 33 | ## 0.7.5 - 2019-04-18 34 | * Refix Send web hook after program (re)run #18 35 | 36 | ## 0.7.4 - 2019-04-18 37 | * UNIX: Fixed easyGetAllProjPaths #22 38 | 39 | ## 0.7.3 - 2019-04-17 40 | * Send web hook after program (re)run #18 41 | 42 | ## 0.7.2 - 2019-04-13 43 | * Refix Allows passing arguments to binary through fcswatch tool #14 44 | 45 | ## 0.7.1 - 2019-04-13 46 | * Allows passing arguments to binary through fcswatch tool #14 47 | 48 | ## 0.7.0 - 2019-04-12 49 | * Port out FcsWatch.Core used by FcsWatch.Binary and FcsWatch.Prota 50 | * Mark a lot unrelated types as internal and private 51 | * Shared file should trigger both projects compiling #11 52 | * Change Some Post to PostAndReply (more easy to test) 53 | * Try to add porta part to current solution 54 | 55 | ## 0.6.5 - 2019-03-24 56 | * Kill running project after watch mode is interrupt 57 | 58 | ## 0.6.4 - 2019-03-24 59 | * Pack as tool 60 | 61 | ## 0.6.3 - 2019-03-24 62 | * Using shared file between cli 63 | 64 | ## 0.6.2 - 2019-03-24 65 | * PrivateAssets FcsWatch-Helper.csproj 66 | 67 | ## 0.6.1 - 2019-03-24 68 | * Nuget package Include FcsWatch.Helpers 69 | 70 | ## 0.6.0 - 2019-03-24 71 | * Added autoReload mode 72 | * FcsWatch Cli Support 73 | 74 | ## 0.5.1 - 2019-03-18 75 | * BUGFIX: (Fetch full framework Cracked fsproj) List.exists -> List.forAll 76 | * Using FPublisher cli 77 | 78 | ## 0.5.0 - 2019-02-23 79 | * Multiple target frameworks 80 | * Refator to more readable codes 81 | * Deep copy dlls from obj to bin 82 | * Remove Unnessary dependecies 83 | * Using lightweight server --- Suave 84 | * FPublisher support 85 | 86 | ## 0.4.0 - 2019-01-23 87 | * Compile multiple files at some time(multiple projects) 88 | 89 | ## 0.3.0 - 2019-01-22 90 | * Added At once watch mode 91 | * Added Plugin mode 92 | 93 | ## 0.2.0 - 2019-01-17 94 | * Add fs file to project without interrupting watcher 95 | 96 | ## 0.1.2 - 2019-01-15 97 | * Refector to actor model 98 | * Warm compile 99 | * Add nuget icon to README 100 | 101 | ## 0.1.1-beta00023 - 2019-01-15 102 | * First Draft Version 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | node_modules 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.sln.docstates 10 | 11 | # Xamarin Studio / monodevelop user-specific 12 | *.userprefs 13 | *.dll.mdb 14 | *.exe.mdb 15 | tests/FsLive.Porta.Cli.Tests/data/** 16 | 17 | # Build results 18 | 19 | [Dd]ebug/ 20 | [Rr]elease/ 21 | x64/ 22 | build/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | 26 | # MSTest test Results 27 | [Tt]est[Rr]esult*/ 28 | [Bb]uild[Ll]og.* 29 | 30 | *_i.c 31 | *_p.c 32 | *.ilk 33 | *.meta 34 | *.obj 35 | *.pch 36 | *.pdb 37 | *.pgc 38 | *.pgd 39 | *.rsp 40 | *.sbr 41 | *.tlb 42 | *.tli 43 | *.tlh 44 | *.tmp 45 | *.tmp_proj 46 | *.log 47 | *.vspscc 48 | *.vssscc 49 | .builds 50 | *.pidb 51 | *.log 52 | *.scc 53 | 54 | # Visual C++ cache files 55 | ipch/ 56 | *.aps 57 | *.ncb 58 | *.opensdf 59 | *.sdf 60 | *.cachefile 61 | 62 | # Visual Studio profiler 63 | *.psess 64 | *.vsp 65 | *.vspx 66 | 67 | # Other Visual Studio data 68 | .vs/ 69 | 70 | # Guidance Automation Toolkit 71 | *.gpState 72 | 73 | # ReSharper is a .NET coding add-in 74 | _ReSharper*/ 75 | *.[Rr]e[Ss]harper 76 | 77 | # TeamCity is a build add-in 78 | _TeamCity* 79 | 80 | # DotCover is a Code Coverage Tool 81 | *.dotCover 82 | 83 | # NCrunch 84 | *.ncrunch* 85 | .*crunch*.local.xml 86 | 87 | # Installshield output folder 88 | [Ee]xpress/ 89 | 90 | # DocProject is a documentation generator add-in 91 | DocProject/buildhelp/ 92 | DocProject/Help/*.HxT 93 | DocProject/Help/*.HxC 94 | DocProject/Help/*.hhc 95 | DocProject/Help/*.hhk 96 | DocProject/Help/*.hhp 97 | DocProject/Help/Html2 98 | DocProject/Help/html 99 | 100 | # Click-Once directory 101 | publish/ 102 | 103 | # Publish Web Output 104 | *.Publish.xml 105 | 106 | # Enable nuget.exe in the .nuget folder (though normally executables are not tracked) 107 | !.nuget/NuGet.exe 108 | 109 | # Windows Azure Build Output 110 | csx 111 | *.build.csdef 112 | 113 | # Windows Store app package directory 114 | AppPackages/ 115 | 116 | 117 | # Others 118 | sql/ 119 | *.Cache 120 | ClientBin/ 121 | [Ss]tyle[Cc]op.* 122 | ~$* 123 | *~ 124 | *.dbmdl 125 | *.[Pp]ublish.xml 126 | *.pfx 127 | *.publishsettings 128 | 129 | # RIA/Silverlight projects 130 | Generated_Code/ 131 | 132 | # Backup & report files from converting an old project file to a newer 133 | # Visual Studio version. Backup files are not needed, because we have git ;-) 134 | _UpgradeReport_Files/ 135 | Backup*/ 136 | UpgradeLog*.XML 137 | UpgradeLog*.htm 138 | 139 | # SQL Server files 140 | App_Data/*.mdf 141 | App_Data/*.ldf 142 | 143 | 144 | #LightSwitch generated files 145 | GeneratedArtifacts/ 146 | _Pvt_Extensions/ 147 | ModelManifest.xml 148 | 149 | # ========================= 150 | # Windows detritus 151 | # ========================= 152 | 153 | # Windows image file caches 154 | Thumbs.db 155 | ehthumbs.db 156 | 157 | # Folder config file 158 | Desktop.ini 159 | 160 | # Recycle Bin used on file shares 161 | $RECYCLE.BIN/ 162 | 163 | # Mac desktop service store files 164 | .DS_Store 165 | 166 | # =================================================== 167 | # Exclude F# project specific directories and files 168 | # =================================================== 169 | 170 | # NuGet Packages Directory 171 | packages/ 172 | 173 | # Test results produced by build 174 | TestResults.xml 175 | 176 | # Nuget outputs 177 | nuget/*.nupkg 178 | release.cmd 179 | release.sh 180 | localpackages/ 181 | paket-files 182 | *.orig 183 | docsrc/content/license.md 184 | docsrc/content/release-notes.md 185 | .fake 186 | docsrc/tools/FSharp.Formatting.svclog 187 | 188 | .ionide/ -------------------------------------------------------------------------------- /src/fcswatch-cli/Share.fs: -------------------------------------------------------------------------------- 1 | module FcsWatch.Cli.Share 2 | 3 | open System 4 | open FcsWatch.Core 5 | open FcsWatch.Binary 6 | open Argu 7 | open Fake.IO.Globbing.Operators 8 | open Fake.IO.FileSystemOperators 9 | open Fake.Core 10 | open FcsWatch.Core.Types 11 | 12 | let private defaultUrl = "http://localhost:9867/update" 13 | 14 | type Arguments = 15 | | Working_Dir of string 16 | | Project_File of string 17 | | Debuggable 18 | | Logger_Level of Logger.Level 19 | | No_Build 20 | | Webhook of string 21 | | Send 22 | | [] Framework of string 23 | | [] Configuration of Configuration 24 | with 25 | interface IArgParserTemplate with 26 | member x.Usage = 27 | match x with 28 | | Working_Dir _ -> "Specfic working directory, default is current directory" 29 | | Project_File _ -> "Entry project file, default is exact fsproj file in working dir" 30 | | Debuggable _ -> "Enable debuggable in vscode, This will disable auto Reload" 31 | | Logger_Level _ -> "Default is Minimal" 32 | | No_Build -> "--no-build" 33 | | Webhook _ -> "send a web hook when program (re)run" 34 | | Send _ -> sprintf "Equivalent to --webhook %s" defaultUrl 35 | | Framework _ -> "The target framework to build for. The default to prefer netcore." 36 | | Configuration _ -> "(experienment)The configuration to use for building the project. The default is Debug." 37 | 38 | type ProcessResult = 39 | { Config: BinaryConfig 40 | ProjectFile: string } 41 | 42 | let processParseResults additionalBinaryArgs (results: ParseResults) = 43 | let execContext = Fake.Core.Context.FakeExecutionContext.Create false "generate.fsx" [] 44 | Fake.Core.Context.setExecutionContext (Fake.Core.Context.RuntimeContext.Fake execContext) 45 | let defaultConfigValue = BinaryConfig.DefaultValue 46 | 47 | let workingDir = results.GetResult (Working_Dir,defaultConfigValue.WorkingDir) 48 | 49 | let projectFile = 50 | match results.TryGetResult Project_File with 51 | | Some projectFile -> projectFile 52 | | None -> 53 | (!! (workingDir "*.fsproj") 54 | |> Seq.filter (fun file -> file.EndsWith ".fsproj") 55 | |> List.ofSeq 56 | |> function 57 | | [ ] -> 58 | failwithf "no project file found, no compilation arguments given and no project file found in \"%s\"" Environment.CurrentDirectory 59 | | [ file ] -> 60 | printfn "using implicit project file '%s'" file 61 | file 62 | | file1 :: file2 :: _ -> 63 | failwithf "multiple project files found, e.g. %s and %s" file1 file2 ) 64 | 65 | let noBuild = 66 | match results.TryGetResult No_Build with 67 | | Some _ -> true 68 | | None -> 69 | false 70 | 71 | let webhook = 72 | match results.TryGetResult Send, results.TryGetResult Webhook with 73 | | Some _, _ -> Some defaultUrl 74 | | _, Some webhook -> Some webhook 75 | | _ -> None 76 | 77 | { ProjectFile = projectFile 78 | Config = 79 | { BinaryConfig.DefaultValue with 80 | WorkingDir = workingDir 81 | DevelopmentTarget = 82 | match results.TryGetResult Debuggable with 83 | | Some _ -> DevelopmentTarget.debuggableProgram 84 | | None -> DevelopmentTarget.autoReloadProgram 85 | 86 | LoggerLevel = results.GetResult(Logger_Level, defaultConfigValue.LoggerLevel) 87 | NoBuild = noBuild 88 | Framework = results.TryGetResult Framework 89 | Configuration = results.GetResult(Configuration, BinaryConfig.DefaultValue.Configuration) 90 | Webhook = webhook 91 | AdditionalSwitchArgs = additionalBinaryArgs } 92 | } 93 | 94 | 95 | 96 | 97 | let parser = ArgumentParser.Create(programName = "fcswatch.exe") 98 | -------------------------------------------------------------------------------- /.paket/paket.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | $(MSBuildThisFileDirectory) 8 | $(MSBuildThisFileDirectory)..\ 9 | $(PaketRootPath)paket.lock 10 | $(PaketRootPath)paket-files\paket.restore.cached 11 | /Library/Frameworks/Mono.framework/Commands/mono 12 | mono 13 | 14 | 15 | 16 | 17 | $(PaketRootPath)paket.exe 18 | $(PaketToolsPath)paket.exe 19 | "$(PaketExePath)" 20 | $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" 21 | 22 | 23 | 24 | 25 | 26 | $(MSBuildProjectFullPath).paket.references 27 | 28 | 29 | 30 | 31 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references 32 | 33 | 34 | 35 | 36 | $(MSBuildProjectDirectory)\paket.references 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | $(PaketCommand) restore --references-file "$(PaketReferences)" 49 | 50 | RestorePackages; $(BuildDependsOn); 51 | 52 | 53 | 54 | true 55 | 56 | 57 | 58 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) 59 | $([System.IO.File]::ReadAllText('$(PaketLockFilePath)')) 60 | true 61 | false 62 | true 63 | 64 | 65 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/FcsWatch.Porta/CompilerTmpEmitter.fs: -------------------------------------------------------------------------------- 1 | namespace FcsWatch.Porta 2 | open Fake.Core 3 | open FcsWatch.Core 4 | open Types 5 | open FcsWatch.Core.CrackedFsproj 6 | open FcsWatch.Core.CompilerTmpEmitter 7 | open FSharp.Compiler.SourceCodeServices 8 | open FcsWatch.Porta.FromCompilerService 9 | open Extensions 10 | 11 | type PortaConfig = 12 | { Eval: bool 13 | LiveCheckOnly: bool 14 | LoggerLevel: Logger.Level 15 | Fsproj: string option 16 | UseEditFiles: bool 17 | WriteInfo: bool 18 | NoBuild: bool 19 | WorkingDir: string 20 | Webhook: string option 21 | Watch: bool 22 | OtherFlags: string [] } 23 | 24 | 25 | 26 | 27 | 28 | [] 29 | module CompilerTmpEmitter = 30 | type TmpEmitterState = CompilerTmpEmitterState 31 | 32 | let mutable private count = 0 33 | 34 | [] 35 | module CompilerTmpEmitterState = 36 | 37 | let private processResult config projOptions (result: CheckResult) = 38 | if result.ExitCode <> 0 then () 39 | else 40 | match config.Webhook with 41 | | Some hook -> 42 | sendToWebHook config.Eval hook result.Contents 43 | | None -> 44 | 45 | if config.Eval then 46 | printfn "fscd: CHANGE DETECTED, RE-EVALUATING ALL INPUTS...." 47 | evaluateDecls config.Eval config.WriteInfo config.LiveCheckOnly result.Contents projOptions 48 | 49 | // The default is to dump 50 | if not config.Eval && config.Webhook.IsNone then 51 | let fileConvContents = jsonFiles config.Eval (Array.ofList result.Contents) 52 | count <- count + 1 53 | logger.Info "%A\nCount:%d" fileConvContents count 54 | 55 | 56 | let tryEmit (config: PortaConfig) (state: TmpEmitterState) = 57 | let commonState = state.CommonState 58 | 59 | let cache = commonState.CrackerFsprojFileBundleCache 60 | 61 | match commonState.CompilingNumber with 62 | | 0 -> 63 | 64 | logger.Info "Current cached compier task is %d" commonState.CachedCompilerTasks.Length 65 | 66 | match commonState.CachedCompilerTasks with 67 | | _ -> 68 | let lastTasks = 69 | commonState.CachedCompilerTasks 70 | |> List.groupBy (fun compilerTask -> 71 | compilerTask.Task.Result.[0].Fsproj.ProjPath 72 | ) 73 | |> List.map (fun (projPath, compilerTasks) -> 74 | compilerTasks |> List.maxBy (fun compilerTask -> compilerTask.StartTime) 75 | ) 76 | 77 | 78 | let allResults: CheckResult list = lastTasks |> List.collect (fun task -> task.Task.Result) 79 | 80 | match List.tryFind (fun (result: CheckResult) -> result.ExitCode <> 0) allResults with 81 | | Some result -> 82 | let errorText = 83 | result.Errors 84 | |> Seq.map (fun error -> error.ToString()) 85 | |> String.concat "\n" 86 | 87 | state 88 | 89 | | None -> 90 | 91 | let projRefersMap = cache.ProjRefersMap 92 | 93 | let projLevelMap = cache.ProjLevelMap 94 | 95 | commonState.CompilerTmp 96 | |> Seq.sortByDescending (fun projPath -> 97 | projLevelMap.[projPath] 98 | ) 99 | |> Seq.iter (fun projPath -> 100 | let correspondingResult = 101 | allResults |> List.find (fun result -> result.Fsproj.ProjPath = projPath) 102 | 103 | processResult config correspondingResult.Fsproj.FSharpProjectOptions correspondingResult 104 | ) 105 | CompilerTmpEmitterState.createEmpty 0 cache 106 | | _ -> state 107 | 108 | 109 | let create config = 110 | { new ICompilerTmpEmitter with 111 | member x.TryEmit(workingDir, state) = CompilerTmpEmitterState.tryEmit config state 112 | member x.ProcessCustomMsg (_, state) = state 113 | member x.CustomInitialState = 0 } 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/FcsWatch.Porta.Interpreter/CodeModel.fs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Fabulous contributors. See LICENSE.md for license. 2 | 3 | module FcsWatch.Porta.CodeModel 4 | 5 | type DRange = 6 | { File: string 7 | StartLine: int 8 | StartColumn: int 9 | EndLine: int 10 | EndColumn: int } 11 | 12 | /// A representation of resolved F# expressions that can be serialized 13 | type DExpr = 14 | | Value of DLocalRef 15 | | ThisValue of DType 16 | | BaseValue of DType 17 | | Application of DExpr * DType[] * DExpr[] * DRange option 18 | | Lambda of DType * DType * DLocalDef * DExpr 19 | | TypeLambda of DGenericParameterDef[] * DExpr 20 | | Quote of DExpr 21 | | IfThenElse of DExpr * DExpr * DExpr 22 | | DecisionTree of DExpr * (DLocalDef[] * DExpr)[] 23 | | DecisionTreeSuccess of int * DExpr[] 24 | | Call of DExpr option * DMemberRef * DType[] * DType[] * DExpr[] * DRange option 25 | | NewObject of DMemberRef * DType[] * DExpr[] 26 | | LetRec of ( DLocalDef * DExpr)[] * DExpr 27 | | Let of (DLocalDef * DExpr) * DExpr 28 | | NewRecord of DType * DExpr[] 29 | | ObjectExpr of DType * DExpr * DObjectExprOverrideDef[] * (DType * DObjectExprOverrideDef[])[] 30 | | FSharpFieldGet of DExpr option * DType * DFieldRef 31 | | FSharpFieldSet of DExpr option * DType * DFieldRef * DExpr 32 | | NewUnionCase of DType * DUnionCaseRef * DExpr[] 33 | | UnionCaseGet of DExpr * DType * DUnionCaseRef * DFieldRef 34 | | UnionCaseSet of DExpr * DType * DUnionCaseRef * DFieldRef * DExpr 35 | | UnionCaseTag of DExpr * DType 36 | | UnionCaseTest of DExpr * DType * DUnionCaseRef 37 | | TraitCall of DType[] * string * isInstance: bool * DType[] * DType[] * DExpr[] * DRange option 38 | | NewTuple of DType * DExpr[] 39 | | TupleGet of DType * int * DExpr 40 | | Coerce of DType * DExpr 41 | | NewArray of DType * DExpr[] 42 | | TypeTest of DType * DExpr 43 | | AddressSet of DExpr * DExpr 44 | | ValueSet of Choice * DExpr 45 | | Unused 46 | | DefaultValue of DType 47 | | Const of obj * DType 48 | | AddressOf of DExpr 49 | | Sequential of DExpr * DExpr 50 | | FastIntegerForLoop of DExpr * DExpr * DExpr * bool 51 | | WhileLoop of DExpr * DExpr 52 | | TryFinally of DExpr * DExpr 53 | | TryWith of DExpr * DLocalDef * DExpr * DLocalDef * DExpr 54 | | NewDelegate of DType * DExpr 55 | | ILFieldGet of DExpr option * DType * string 56 | | ILFieldSet of DExpr option * DType * string * DExpr 57 | | ILAsm of string * DType[] * DExpr[] 58 | 59 | and DType = 60 | | DNamedType of DEntityRef * DType[] 61 | | DFunctionType of DType * DType 62 | | DTupleType of bool * DType[] 63 | | DArrayType of int * DType 64 | | DByRefType of DType 65 | | DVariableType of string 66 | 67 | and DLocalDef = 68 | { Name: string 69 | IsMutable: bool 70 | Type: DType 71 | Range: DRange option 72 | IsCompilerGenerated: bool } 73 | 74 | and DMemberDef = 75 | { EnclosingEntity: DEntityRef 76 | Name: string 77 | GenericParameters: DGenericParameterDef[] 78 | IsInstance: bool 79 | IsValue: bool 80 | IsCompilerGenerated: bool 81 | Parameters: DLocalDef[] 82 | ReturnType: DType 83 | Range: DRange option } 84 | 85 | member x.Ref = 86 | { Entity=x.EnclosingEntity 87 | Name= x.Name 88 | GenericArity = x.GenericParameters.Length 89 | ArgTypes = (x.Parameters |> Array.map (fun p -> p.Type)) 90 | ReturnType = x.ReturnType 91 | Range = x.Range } 92 | 93 | and DGenericParameterDef = 94 | { Name: string } 95 | 96 | and DEntityDef = 97 | { Name: string 98 | GenericParameters: DGenericParameterDef[] 99 | UnionCases: string[] 100 | Range: DRange option } 101 | 102 | and DEntityRef = DEntityRef of string 103 | 104 | and DMemberRef = 105 | { Entity: DEntityRef 106 | Name: string 107 | GenericArity: int 108 | ArgTypes: DType[] 109 | ReturnType: DType 110 | Range: DRange option } 111 | 112 | and DLocalRef = 113 | { Name: string 114 | IsThisValue: bool 115 | IsMutable: bool 116 | Range: DRange option } 117 | 118 | and DFieldRef = DFieldRef of int * string 119 | 120 | and DUnionCaseRef = DUnionCaseRef of string 121 | 122 | and DObjectExprOverrideDef = 123 | { Name: string 124 | GenericParameters: DGenericParameterDef[] 125 | Parameters: DLocalDef[] 126 | Body: DExpr } 127 | 128 | type DDecl = 129 | | DDeclEntity of DEntityDef * DDecl[] 130 | | DDeclMember of DMemberDef * DExpr * isLiveCheck: bool 131 | | InitAction of DExpr * DRange option 132 | 133 | type DFile = 134 | { Code: DDecl[] } 135 | 136 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.API/Masse.API.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | MasseAPI 7 | true 8 | Lambda 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | PreserveNewest 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/FcsWatch.Binary/Extensions.fs: -------------------------------------------------------------------------------- 1 | namespace FcsWatch.Binary 2 | open System.IO 3 | open Fake.IO 4 | open Fake.IO.FileSystemOperators 5 | open FcsWatch.Core.CrackedFsproj 6 | open FSharp.Compiler.SourceCodeServices 7 | open FcsWatch.Core 8 | open Fake.DotNet 9 | open Fake.Core 10 | open FcsWatch.Core.Types 11 | 12 | type CompilerResult = 13 | { Dll: string 14 | Errors: FSharpErrorInfo [] 15 | ExitCode: int 16 | ProjPath: string } 17 | with 18 | member x.Pdb = Path.changeExtension ".pdb" x.Dll 19 | 20 | interface ICompilerOrCheckResult with 21 | member x.Errors = x.Errors 22 | member x.ExitCode = x.ExitCode 23 | member x.ProjPath = x.ProjPath 24 | 25 | [] 26 | module internal Global = 27 | let mutable logger = Logger.create Logger.Level.Minimal 28 | 29 | let private dotnetWith command args dir = 30 | DotNet.exec 31 | (fun ops -> {ops with WorkingDirectory = dir}) 32 | command 33 | (Args.toWindowsCommandLine args) 34 | 35 | let dotnet command args dir = 36 | let result = dotnetWith command args dir 37 | if result.ExitCode <> 0 38 | then failwithf "Error while running %s with args %A" command (List.ofSeq args) 39 | 40 | 41 | module Extensions = 42 | 43 | 44 | type internal Logger.Logger with 45 | member x.CopyFile src dest = 46 | File.Copy(src,dest,true) 47 | logger.Important "%s ->\n%s" src dest 48 | 49 | /// In release configuration, still copy pdb 50 | member x.CopyPdb _configuration src dest = 51 | x.CopyFile src dest 52 | 53 | [] 54 | module SingleTargetCrackedFsproj = 55 | 56 | 57 | let copyFileFromRefDllToBin (configuration: Configuration) originProjectFile (destCrackedFsprojSingleTarget: SingleTargetCrackedFsproj) = 58 | 59 | let targetDir = destCrackedFsprojSingleTarget.TargetDir 60 | 61 | let originDll = 62 | let projName = Path.GetFileNameWithoutExtension originProjectFile 63 | 64 | destCrackedFsprojSingleTarget.RefDlls 65 | |> Array.find(fun refDll -> Path.GetFileNameWithoutExtension refDll = projName) 66 | 67 | let fileName = Path.GetFileName originDll 68 | 69 | let destDll = targetDir fileName 70 | 71 | logger.CopyFile originDll destDll 72 | 73 | logger.CopyPdb configuration (Path.changeExtension ".pdb" originDll) (Path.changeExtension ".pdb" destDll) 74 | 75 | destCrackedFsprojSingleTarget.AdditionalTargetDirs |> Seq.iter (fun targetDir -> 76 | let destDll = targetDir fileName 77 | logger.CopyFile originDll destDll 78 | logger.CopyPdb configuration (Path.changeExtension ".pdb" originDll) (Path.changeExtension ".pdb" destDll)) 79 | 80 | 81 | let copyObjToBin configuration (singleTargetCrackedFsproj: SingleTargetCrackedFsproj) = 82 | logger.CopyFile singleTargetCrackedFsproj.ObjTargetFile singleTargetCrackedFsproj.TargetPath 83 | 84 | logger.CopyPdb configuration singleTargetCrackedFsproj.ObjTargetPdb singleTargetCrackedFsproj.TargetPdbPath 85 | 86 | singleTargetCrackedFsproj.AdditionalTargetDirs |> Seq.iter (fun targetDir -> 87 | logger.CopyFile singleTargetCrackedFsproj.ObjTargetFile targetDir 88 | logger.CopyPdb configuration singleTargetCrackedFsproj.ObjTargetPdb targetDir) 89 | 90 | 91 | let compile (checker: FSharpChecker) (crackedProjectSingleTarget: SingleTargetCrackedFsproj) = async { 92 | let tmpDll = crackedProjectSingleTarget.ObjTargetFile 93 | 94 | let baseOptions = 95 | crackedProjectSingleTarget.FSharpProjectOptions.OtherOptions 96 | |> Array.map (fun op -> if op.StartsWith "-o:" then "-o:" + tmpDll else op) 97 | 98 | let fscArgs = Array.concat [[|"fsc.exe"|]; baseOptions;[|"--nowin32manifest"|]] 99 | let! errors,exitCode = checker.Compile(fscArgs) 100 | return 101 | { Errors = errors 102 | ExitCode = exitCode 103 | Dll = tmpDll 104 | ProjPath = crackedProjectSingleTarget.ProjPath } 105 | } 106 | 107 | [] 108 | module CrackedFsproj = 109 | 110 | let copyFileFromRefDllToBin configuration projectFile (destCrackedFsproj: CrackedFsproj) = 111 | destCrackedFsproj.AsList 112 | |> List.iter (SingleTargetCrackedFsproj.copyFileFromRefDllToBin configuration projectFile) 113 | 114 | let copyObjToBin configuration (crackedFsproj: CrackedFsproj) = 115 | crackedFsproj.AsList |> List.iter (SingleTargetCrackedFsproj.copyObjToBin configuration) 116 | 117 | 118 | let compile (checker: FSharpChecker) (crackedFsProj: CrackedFsproj) = 119 | crackedFsProj.AsList 120 | |> List.map (SingleTargetCrackedFsproj.compile checker) 121 | |> Async.Parallel 122 | 123 | [] 124 | module internal InternalExtensions = 125 | [] 126 | module File = 127 | let rec tryFindUntilRoot makePath dir = 128 | let file = makePath dir 129 | match file with 130 | | null -> None 131 | | _ -> 132 | if File.exists file 133 | then Some file 134 | else tryFindUntilRoot makePath (Path.getDirectory dir) -------------------------------------------------------------------------------- /src/FcsWatch.Porta/Extensions.fs: -------------------------------------------------------------------------------- 1 | namespace FcsWatch.Porta 2 | 3 | open System.IO 4 | open Fake.IO 5 | open FcsWatch.Core.CrackedFsproj 6 | open FSharp.Compiler.SourceCodeServices 7 | open FcsWatch.Core 8 | open FcsWatch.Core.Types 9 | 10 | type CheckResult = 11 | { ExitCode: int 12 | Errors: FSharpErrorInfo [] 13 | Fsproj: SingleTargetCrackedFsproj 14 | Contents: FSharpImplementationFileContents list } 15 | with 16 | interface ICompilerOrCheckResult with 17 | member x.ExitCode = x.ExitCode 18 | member x.Errors = x.Errors 19 | member x.ProjPath = x.Fsproj.ProjPath 20 | 21 | [] 22 | module internal Global = 23 | let mutable logger = Logger.create Logger.Level.Minimal 24 | 25 | module Extensions = 26 | 27 | [] 28 | module SingleTargetCrackedFsproj = 29 | 30 | let private mapProjOptionsWithoutSourceFiles (projOptions: FSharpProjectOptions) = 31 | { projOptions with 32 | OtherOptions = 33 | projOptions.OtherOptions 34 | |> Array.filter (fun (op: string) -> not (op.EndsWith ".fs" || op.EndsWith ".fsi" || op.EndsWith ".fsx")) 35 | } 36 | let check useEditFiles liveCheckOnly (checker: FSharpChecker) (singleTargetCrackedFsproj: SingleTargetCrackedFsproj) = async { 37 | let rec checkFile1 count sourceFile = 38 | try 39 | let _, checkResults = checker.ParseAndCheckFileInProject(sourceFile, 0, FileSystem.readFile sourceFile useEditFiles, mapProjOptionsWithoutSourceFiles singleTargetCrackedFsproj.FSharpProjectOptions) |> Async.RunSynchronously 40 | match checkResults with 41 | | FSharpCheckFileAnswer.Aborted -> 42 | logger.Important "aborted" 43 | Result.Error (None,[||]) 44 | 45 | | FSharpCheckFileAnswer.Succeeded res -> 46 | let mutable hasErrors = false 47 | for error in res.Errors do 48 | if error.Severity = FSharpErrorSeverity.Error then 49 | hasErrors <- true 50 | 51 | if hasErrors then 52 | Result.Error (res.ImplementationFile, res.Errors) 53 | else 54 | Result.Ok res.ImplementationFile 55 | with 56 | | :? System.IO.IOException when count = 0 -> 57 | System.Threading.Thread.Sleep 500 58 | checkFile1 1 sourceFile 59 | | exn -> 60 | logger.Error "%s" (exn.ToString()) 61 | Result.Error (None,[||]) 62 | 63 | let checkFile2 file = 64 | match checkFile1 0 (Path.GetFullPath(file)) with 65 | // Note, if livechecks are on, we continue on regardless of errors 66 | | Result.Error (iopt, errors) when not liveCheckOnly -> 67 | logger.Important "fscd: ERRORS for %s" file 68 | Result.Error errors 69 | | Result.Error (iopt, errors) -> 70 | match iopt with 71 | | None -> Result.Error errors 72 | | Some i -> 73 | logger.Important "fscd: GOT PortaCode for %s" file 74 | Result.Ok (i,errors) 75 | | Result.Ok iopt -> 76 | match iopt with 77 | | None -> Result.Error [||] 78 | | Some i -> 79 | logger.Important "fscd: GOT PortaCode for %s" file 80 | Result.Ok (i, [||]) 81 | 82 | let sourceFiles = singleTargetCrackedFsproj.SourceFiles 83 | let allResults = 84 | sourceFiles 85 | |> Array.map checkFile2 86 | 87 | let isError result = 88 | match result with 89 | | Result.Error _ -> true 90 | | Result.Ok _ -> false 91 | 92 | return 93 | if Array.exists isError allResults 94 | then 95 | { ExitCode = -1 96 | Errors = 97 | allResults |> Array.choose (fun result -> 98 | match result with 99 | | Result.Error errors -> Some (errors) 100 | | Result.Ok (_,errors) -> Some (errors) 101 | ) 102 | |> Array.concat 103 | Fsproj = singleTargetCrackedFsproj 104 | Contents = [] } 105 | else 106 | let contents, errors = 107 | allResults 108 | |> Array.choose (fun result -> 109 | match result with 110 | | Result.Error _ -> None 111 | | Result.Ok (contents, errors) -> Some (contents, errors) 112 | ) 113 | |> Array.unzip 114 | 115 | { ExitCode = 0 116 | Contents = List.ofArray contents 117 | Errors = Array.concat errors 118 | Fsproj = singleTargetCrackedFsproj } 119 | } 120 | 121 | 122 | [] 123 | module CrackedFsproj = 124 | 125 | let check useEditFiles liveCheckOnly (checker: FSharpChecker) (crackedFsproj: CrackedFsproj) = 126 | SingleTargetCrackedFsproj.check useEditFiles liveCheckOnly checker crackedFsproj.PreferSingleTargetCrackedFsproj 127 | 128 | -------------------------------------------------------------------------------- /src/fslive-cli/fslive.fs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Fabulous contributors. See LICENSE.md for license. 2 | 3 | // F# Compiler Daemon sample 4 | // 5 | // Sample use, assumes app has a reference to ELmish.XamrinForms.LiveUpdate: 6 | // 7 | // cd Fabulous\Samples\CounterApp\CounterApp 8 | // adb -d forward tcp:9867 tcp:9867 9 | // dotnet run --project ..\..\..\Fabulous.Cli\Fabulous.Cli.fsproj -- --eval @out.args 10 | // dotnet run --project ..\..\..\Fabulous.Cli\Fabulous.Cli.fsproj -- --watch --webhook:http://localhost:9867/update @out.args 11 | 12 | module FsLive.Driver 13 | 14 | open FcsWatch.Porta 15 | open FcsWatch.Core 16 | open System.IO 17 | open System 18 | 19 | 20 | 21 | #if !TEST 22 | [] 23 | #endif 24 | let main (argv: string[]) = 25 | 26 | let mutable fsproj = None 27 | let mutable eval = false 28 | let mutable livechecksonly = false 29 | let mutable watch = false 30 | let mutable useEditFiles = false 31 | let mutable loggerLevel = Logger.Level.Minimal 32 | let mutable writeinfo = false 33 | let mutable webhook = None 34 | let mutable otherFlags = [] 35 | let defaultUrl = "http://localhost:9867/update" 36 | let fsharpArgs = 37 | let mutable haveDashes = false 38 | 39 | [| for arg in argv do 40 | let arg = arg.Trim() 41 | if arg.StartsWith("@") then 42 | for line in File.ReadAllLines(arg.[1..]) do 43 | let line = line.Trim() 44 | if not (String.IsNullOrWhiteSpace(line)) then 45 | yield line 46 | elif arg.EndsWith(".fsproj") then 47 | fsproj <- Some arg 48 | elif arg = "--" then haveDashes <- true 49 | elif arg.StartsWith "--define:" then otherFlags <- otherFlags @ [ arg ] 50 | elif arg = "--watch" then watch <- true 51 | elif arg = "--eval" then eval <- true 52 | elif arg = "--livechecksonly" then livechecksonly <- true 53 | elif arg = "--writeinfo" then writeinfo <- true 54 | elif arg = "--vshack" then useEditFiles <- true 55 | elif arg.StartsWith "--webhook:" then webhook <- Some arg.["--webhook:".Length ..] 56 | elif arg.StartsWith "--loggerlevel:" then 57 | loggerLevel <- 58 | match arg.["--loggerlevel:".Length ..] with 59 | | "0" -> Logger.Level.Quiet 60 | | "1" -> Logger.Level.Minimal 61 | | "2" -> Logger.Level.Normal 62 | | s -> failwithf "invalid logger level number %s" s 63 | 64 | elif arg = "--send" then webhook <- Some defaultUrl 65 | elif arg = "--version" then 66 | printfn "" 67 | printfn "*** NOTE: if sending the code to a device the versions of CodeModel.fs and Interpreter.fs on the device must match ***" 68 | printfn "" 69 | printfn "CLI tool assembly version: %A" (System.Reflection.Assembly.GetExecutingAssembly().GetName().Version) 70 | printfn "CLI tool name: %s" (System.Reflection.Assembly.GetExecutingAssembly().GetName().Name) 71 | printfn "" 72 | elif arg = "--help" then 73 | printfn "Command line tool for watching and interpreting F# projects" 74 | printfn "" 75 | printfn "Usage: arg .. arg [-- ]" 76 | printfn " @args.rsp [-- ]" 77 | printfn " ... Project.fsproj ... [-- ]" 78 | printfn "" 79 | printfn "The default source is a single project file in the current directory." 80 | printfn "The default output is a JSON dump of the PortaCode." 81 | printfn "" 82 | printfn "Arguments:" 83 | printfn " --watch Watch the source files of the project for changes" 84 | printfn " --webhook: Send the JSON-encoded contents of the PortaCode to the webhook" 85 | printfn " --send Equivalent to --webhook:%s" defaultUrl 86 | printfn " --eval Evaluate the contents using the interpreter after each update" 87 | printfn " --livechecksonly (Experimental) Only evaluate declarations with a LiveCheck attribute" 88 | printfn " This uses on-demand execution semantics for top-level declarations" 89 | printfn " --loggerlevel:<0|1|2> 0: Quiet; 1: Minimal; 2: Normal; Default is Minimal" 90 | printfn " --writeinfo (Experimental) Write an info file based on results of evaluation" 91 | printfn " --vshack (Experimental) Watch for .fsharp/foo.fsx.edit files and use the contents of those" 92 | printfn " All other args are assumed to be extra F# command line arguments" 93 | exit 1 94 | else yield arg |] 95 | 96 | 97 | let config: PortaConfig = 98 | { Eval = eval 99 | LiveCheckOnly = livechecksonly 100 | LoggerLevel = loggerLevel 101 | Fsproj = fsproj 102 | UseEditFiles = useEditFiles 103 | WriteInfo = writeinfo 104 | NoBuild = true 105 | WorkingDir = Directory.GetCurrentDirectory() 106 | Webhook = webhook 107 | Watch = watch 108 | OtherFlags = Array.append fsharpArgs (Array.ofList otherFlags) } 109 | 110 | try 111 | System.Environment.SetEnvironmentVariable("LIVECHECK", "1") 112 | runFcsWatcher config 113 | 0 114 | with e -> 115 | printfn "Error: %s" (e.ToString()) 116 | 1 117 | -------------------------------------------------------------------------------- /src/FcsWatch.Core/Compiler.fs: -------------------------------------------------------------------------------- 1 | module FcsWatch.Core.Compiler 2 | open Types 3 | open System.Diagnostics 4 | open FcsWatch.Core.CompilerTmpEmitter 5 | open System 6 | open FcsWatch.Core.CrackedFsproj 7 | open FSharp.Compiler.SourceCodeServices 8 | 9 | 10 | 11 | [] 12 | type CompilerMsg = 13 | | UpdateCache of CrackedFsprojBundleCache 14 | | CompileProjects of why: WhyCompile * projects: CrackedFsproj list * incrCompilingNumberChannel: AsyncReplyChannel 15 | 16 | type CompilerModel = 17 | { CrackerFsprojBundleCache: CrackedFsprojBundleCache } 18 | 19 | 20 | type ICompiler<'Result when 'Result :> ICompilerOrCheckResult> = 21 | abstract member Compile : checker: FSharpChecker * proejct: CrackedFsproj -> Async<'Result []> 22 | abstract member WarmCompile: bool 23 | abstract member Summary: result: 'Result * elapsed: int64 -> string 24 | 25 | let compilerAgent (compiler: ICompiler<'Result>) (compilerTmpEmitterAgent: MailboxProcessor>) (initialCache: CrackedFsprojBundleCache) checker = MailboxProcessor.Start(fun inbox -> 26 | inbox.Error.Add(fun error -> logger.Error "%A" error) 27 | 28 | let createCompileTask (crackedFsprojs: CrackedFsproj list) = 29 | crackedFsprojs 30 | |> List.map (fun crackedFsproj -> 31 | async { 32 | let stopWatch = Stopwatch.StartNew() 33 | 34 | let! compilerResults = compiler.Compile (checker, crackedFsproj) 35 | 36 | match Array.tryFind (fun (compilerResult: 'Result) -> compilerResult.ExitCode <> 0) compilerResults with 37 | | None -> 38 | let compilerResult = compilerResults.[0] 39 | 40 | ICompilerOrCheckResult.processCompileOrCheckResult compilerResult 41 | 42 | logger.Important "%s" (compiler.Summary (compilerResult, stopWatch.ElapsedMilliseconds)) 43 | 44 | compilerTmpEmitterAgent.Post (CompilerTmpEmitterMsg.addTmp crackedFsproj.ProjPath) 45 | 46 | return compilerResult 47 | 48 | | Some erroCompilerResult -> 49 | ICompilerOrCheckResult.processCompileOrCheckResult erroCompilerResult 50 | 51 | return erroCompilerResult 52 | } 53 | ) 54 | |> Async.Parallel 55 | 56 | 57 | let rec loop model = async { 58 | let projectMap = model.CrackerFsprojBundleCache.ProjectMap 59 | 60 | let! msg = inbox.Receive() 61 | match msg with 62 | | CompilerMsg.CompileProjects (why, crackedFsprojs, replyChannel) -> 63 | compilerTmpEmitterAgent.PostAndReply (fun replyChannel -> CompilerTmpEmitterMsg.incrCompilingNum (crackedFsprojs.Length,replyChannel)) 64 | replyChannel.Reply() 65 | 66 | let projPaths = crackedFsprojs |> List.map (fun crackedFsproj -> crackedFsproj.ProjPath) 67 | let m = crackedFsprojs.[0].AsList.[1].FSharpProjectOptions 68 | let p = 69 | m.OtherOptions 70 | |> String.concat "\n" 71 | /// from top to bottom 72 | let rec compileByLevel accResults (projLevelMap: Map) = async { 73 | match projLevelMap.IsEmpty with 74 | | false -> 75 | let inProcess = 76 | let maxLevel = 77 | projLevelMap 78 | |> Seq.map (fun pair -> projLevelMap.[pair.Key]) 79 | |> Seq.max 80 | 81 | projLevelMap 82 | |> Map.filter (fun projPath level -> level = maxLevel) 83 | |> Seq.map (fun projLevelPair -> projectMap.[projLevelPair.Key]) |> List.ofSeq 84 | 85 | let inProcessProjPaths = 86 | inProcess |> Seq.map (fun crackedProject -> 87 | crackedProject.ProjPath 88 | ) |> List.ofSeq 89 | 90 | let! results = createCompileTask inProcess 91 | 92 | let results = 93 | results 94 | |> Array.tryFind(fun result -> 95 | result.ExitCode <> 0 96 | ) 97 | |> function 98 | | Some errorResult -> 99 | [errorResult] 100 | 101 | | None -> 102 | let left = projLevelMap |> Map.filter (fun projPath level -> 103 | not (List.contains projPath inProcessProjPaths) 104 | ) 105 | 106 | compileByLevel (accResults @ (List.ofArray results)) left |> Async.RunSynchronously 107 | 108 | return results 109 | | true -> return accResults 110 | } 111 | 112 | 113 | let startTime = DateTime.Now 114 | 115 | let task = 116 | let correnspondingProjLevelMap = model.CrackerFsprojBundleCache.ProjLevelMap |> Map.filter (fun projPath level -> 117 | List.contains projPath projPaths 118 | ) 119 | 120 | async { 121 | let! result = compileByLevel [] correnspondingProjLevelMap 122 | compilerTmpEmitterAgent.Post (CompilerTmpEmitterMsg.decrCompilingNum crackedFsprojs.Length) 123 | return result 124 | } |> Async.StartAsTask 125 | 126 | compilerTmpEmitterAgent.Post (CompilerTmpEmitterMsg.addTask (CompilerTask (why, startTime, task))) 127 | return! loop model 128 | 129 | | CompilerMsg.UpdateCache cache -> 130 | return! loop { model with CrackerFsprojBundleCache = cache } 131 | 132 | } 133 | 134 | let entryCrackedFsproj = initialCache.EntryCrackedFsproj 135 | 136 | if compiler.WarmCompile then 137 | inbox.PostAndAsyncReply (fun replyChannel -> 138 | CompilerMsg.CompileProjects (WhyCompile.WarmCompile, [entryCrackedFsproj], replyChannel) 139 | ) |> Async.Start 140 | 141 | loop { CrackerFsprojBundleCache = initialCache } 142 | ) -------------------------------------------------------------------------------- /src/FcsWatch.Binary/Main.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FcsWatch.Binary.Main 3 | open FcsWatch.Core 4 | open System.IO 5 | open FcsWatch.Core.Compiler 6 | open FcsWatch.Core.FcsWatcher 7 | open System 8 | open Extensions 9 | open FSharp.Compiler.SourceCodeServices 10 | open Fake.IO.FileSystemOperators 11 | open FcsWatch.Core.Types 12 | 13 | [] 14 | type DevelopmentTarget = 15 | | Debuggable of DebuggingServer.DevelopmentTarget 16 | | AutoReload of AutoReload.DevelopmentTarget 17 | 18 | [] 19 | module DevelopmentTarget = 20 | let debuggableProgram = DevelopmentTarget.Debuggable DebuggingServer.DevelopmentTarget.Program 21 | let autoReloadProgram = DevelopmentTarget.AutoReload AutoReload.DevelopmentTarget.Program 22 | let autoReloadPlugin plugin = DevelopmentTarget.AutoReload (AutoReload.DevelopmentTarget.Plugin plugin) 23 | let debuggablePlugin plugin = DevelopmentTarget.Debuggable (DebuggingServer.DevelopmentTarget.Plugin plugin) 24 | 25 | let (|Plugin|Program|) = function 26 | | DevelopmentTarget.AutoReload autoReload -> 27 | match autoReload with 28 | | AutoReload.DevelopmentTarget.Plugin plugin -> Plugin plugin 29 | | AutoReload.DevelopmentTarget.Program _ -> Program 30 | 31 | | DevelopmentTarget.Debuggable debuggable -> 32 | match debuggable with 33 | | DebuggingServer.DevelopmentTarget.Plugin plugin -> Plugin (DebuggingServer.Plugin.asAutoReloadPlugin plugin) 34 | | DebuggingServer.DevelopmentTarget.Program _ -> Program 35 | 36 | 37 | type BinaryConfig = 38 | { LoggerLevel: Logger.Level 39 | DevelopmentTarget: DevelopmentTarget 40 | WorkingDir: string 41 | NoBuild: bool 42 | Framework: string option 43 | Configuration: Configuration 44 | UseEditFiles: bool 45 | Webhook: string option 46 | WarmCompile: bool 47 | AdditionalSwitchArgs : string array } 48 | 49 | with 50 | static member DefaultValue = 51 | { LoggerLevel = Logger.Level.Minimal 52 | DevelopmentTarget = DevelopmentTarget.autoReloadProgram 53 | WorkingDir = Directory.GetCurrentDirectory() 54 | NoBuild = false 55 | Framework = None 56 | Configuration = Configuration.Debug 57 | UseEditFiles = false 58 | WarmCompile = true 59 | Webhook = None 60 | AdditionalSwitchArgs = Array.empty } 61 | 62 | 63 | [] 64 | module BinaryConfig = 65 | 66 | let additionalBuildArgs (config: BinaryConfig) = 67 | match config.Configuration with 68 | | Configuration.Release -> ["--configuration"; "Release"] 69 | | Configuration.Debug -> ["--configuration"; "Debug"] 70 | 71 | let asAutoReloadProgramRunningArgs whyRun autoReloadDevelopmentTarget (config: BinaryConfig) : AutoReload.ProgramRunningArgs = 72 | { AdditionalSwitchArgs = List.ofArray config.AdditionalSwitchArgs 73 | Webhook = config.Webhook 74 | DevelopmentTarget = autoReloadDevelopmentTarget 75 | WorkingDir = config.WorkingDir 76 | Framework = config.Framework 77 | AdditionalBuildArgs = additionalBuildArgs config 78 | Configuration = config.Configuration 79 | WhyRun = whyRun } 80 | 81 | 82 | let tryBuildProject projectFile (config: BinaryConfig) = 83 | if not config.NoBuild then 84 | let additionalBuildArgs = additionalBuildArgs config 85 | 86 | match config.DevelopmentTarget with 87 | | DevelopmentTarget.Program _ -> 88 | dotnet "build" ([projectFile] @ additionalBuildArgs) config.WorkingDir 89 | 90 | | DevelopmentTarget.Plugin plugin -> 91 | plugin.Unload() 92 | dotnet "build" ([projectFile] @ additionalBuildArgs) config.WorkingDir 93 | 94 | let binaryFcsWatcher (config: BinaryConfig) entryProjectFile = 95 | 96 | logger <- Logger.create config.LoggerLevel 97 | 98 | let config = 99 | { config with 100 | WorkingDir = Path.GetFullPath(config.WorkingDir)} 101 | 102 | let compiler = 103 | let summary projectPath dll elapsed = 104 | sprintf "Summary: 105 | -- origin: %s 106 | -- dest: %s 107 | -- elapsed: %d milliseconds" 108 | projectPath dll elapsed 109 | 110 | { new ICompiler with 111 | member x.Compile(checker, crackedFsproj) = CrackedFsproj.compile checker crackedFsproj 112 | member x.WarmCompile = config.WarmCompile 113 | member x.Summary (result, elapsed) = summary result.ProjPath result.Dll elapsed} 114 | 115 | let coreConfig: FcsWatch.Core.Config = 116 | { LoggerLevel = config.LoggerLevel 117 | WorkingDir = config.WorkingDir 118 | OtherFlags = [||] 119 | Configuration = config.Configuration 120 | UseEditFiles = config.UseEditFiles } 121 | 122 | let checker = FSharpChecker.Create() 123 | 124 | BinaryConfig.tryBuildProject entryProjectFile config 125 | 126 | match config.DevelopmentTarget with 127 | | DevelopmentTarget.AutoReload autoReload -> 128 | 129 | let programRunningArgs: AutoReload.ProgramRunningArgs = 130 | BinaryConfig.asAutoReloadProgramRunningArgs WhyRun.Run autoReload config 131 | 132 | let compilerTmpEmitter = AutoReload.create programRunningArgs 133 | 134 | let fcsWatcher = 135 | fcsWatcherAndCompilerTmpAgent checker compilerTmpEmitter compiler coreConfig (Some entryProjectFile) 136 | |> fst 137 | 138 | let cache = fcsWatcher.PostAndReply FcsWatcherMsg.GetCache 139 | 140 | AutoReload.CrackedFsproj.tryRun programRunningArgs cache.EntryCrackedFsproj 141 | 142 | fcsWatcher 143 | 144 | | DevelopmentTarget.Debuggable debuggable -> 145 | let compilerTmpEmitter = DebuggingServer.create debuggable 146 | let fcsWatcher, compilerTmpEmitterAgent = fcsWatcherAndCompilerTmpAgent checker compilerTmpEmitter compiler coreConfig (Some entryProjectFile) 147 | DebuggingServer.startServer config.WorkingDir compilerTmpEmitterAgent 148 | fcsWatcher 149 | 150 | let tryKill autoReloadDevelopmentTarget entryCrackedFsproj = 151 | AutoReload.CrackedFsproj.tryKill autoReloadDevelopmentTarget entryCrackedFsproj 152 | 153 | let runFcsWatcher (processExit : System.Threading.Tasks.Task) (config: BinaryConfig) entryProjectFile = async { 154 | let binaryFcsWatcher = binaryFcsWatcher config entryProjectFile 155 | do! processExit |> Async.AwaitTask 156 | let cache = binaryFcsWatcher.PostAndReply FcsWatcherMsg.GetCache 157 | match config.DevelopmentTarget with 158 | | DevelopmentTarget.AutoReload autoReload -> tryKill autoReload cache.EntryCrackedFsproj 159 | | _ -> () 160 | 161 | logger.Info "Exited fcswatch.exe" 162 | } 163 | -------------------------------------------------------------------------------- /src/FcsWatch.Binary/DebuggingServer.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FcsWatch.Binary.DebuggingServer 3 | open Fake.IO 4 | open System.Net 5 | open Fake.IO.FileSystemOperators 6 | open System.Text 7 | open FcsWatch.Core 8 | open Suave.Filters 9 | open Suave.Operators 10 | open Suave 11 | open System.Net.Sockets 12 | open FcsWatch.Core.CompilerTmpEmitter 13 | open Extensions 14 | open FcsWatch.Core.Types 15 | open VscodeHelper 16 | 17 | type Plugin = 18 | { Load: unit -> unit 19 | Unload: unit -> unit 20 | Calculate: unit -> unit 21 | TimeDelayAfterUninstallPlugin: int 22 | PluginDebugInfo: PluginDebugInfo } 23 | 24 | [] 25 | module Plugin = 26 | let asAutoReloadPlugin (plugin: Plugin): AutoReload.Plugin = 27 | { Load = plugin.Load 28 | Unload = plugin.Unload 29 | Calculate = plugin.Calculate 30 | TimeDelayAfterUninstallPlugin = plugin.TimeDelayAfterUninstallPlugin 31 | PluginDebugInfo = Some plugin.PluginDebugInfo } 32 | 33 | [] 34 | type DevelopmentTarget = 35 | | Program 36 | | Plugin of Plugin 37 | 38 | 39 | let private freePort() = 40 | let listener = new TcpListener(IPAddress.Any, 0); 41 | listener.Start() 42 | let port = (listener.LocalEndpoint :?> IPEndPoint).Port 43 | listener.Stop() 44 | port 45 | 46 | let private generateCurlCache freePort root = 47 | let cacheDir = root ".fake" "fcswatch" 48 | 49 | let fileName = cacheDir "port.cache" 50 | 51 | Directory.ensure cacheDir 52 | 53 | let lines = 54 | [ sprintf "url: http://localhost:%d/emitCompilerTmp" freePort 55 | "-f" ] 56 | 57 | File.writeWithEncoding Encoding.ASCII false fileName lines 58 | 59 | 60 | type TmpEmitterMsg = TmpEmitterMsg of replyChannel: AsyncReplyChannel 61 | type TmpEmitterState = CompilerTmpEmitterState list, CompilerResult> 62 | 63 | 64 | [] 65 | module internal TmpEmitterState = 66 | open System.Threading 67 | 68 | let tryEmit (developmentTarget: DevelopmentTarget) (tmpEmitterState: TmpEmitterState) = 69 | let emitReplyChannels = tmpEmitterState.CustomState 70 | let commonState = tmpEmitterState.CommonState 71 | let cache = commonState.CrackerFsprojFileBundleCache 72 | logger.Info "tryEmitAction: current emitReplyChannels number is %d" emitReplyChannels.Length 73 | 74 | match commonState.CompilingNumber, emitReplyChannels with 75 | | 0, h::t -> 76 | let replySuccess() = h.Reply (Successful.OK "fcswatch: Ready to debug") 77 | 78 | let replyFailure errorText = h.Reply (RequestErrors.BAD_REQUEST errorText) 79 | 80 | logger.Info "Current valid compier task is %d" commonState.CachedCompilerTasks.Length 81 | 82 | match commonState.CachedCompilerTasks with 83 | | [] -> 84 | replySuccess() 85 | 86 | match developmentTarget with 87 | | DevelopmentTarget.Plugin plugin -> 88 | Thread.Sleep(plugin.PluginDebugInfo.DebuggerAttachTimeDelay) 89 | plugin.Calculate() 90 | | _ -> () 91 | 92 | tmpEmitterState 93 | | _ -> 94 | let lastTasks = 95 | commonState.CachedCompilerTasks 96 | |> List.groupBy (fun compilerTask -> 97 | compilerTask.Task.Result.[0].ProjPath 98 | ) 99 | |> List.map (fun (projPath, compilerTasks) -> 100 | compilerTasks |> List.maxBy (fun compilerTask -> compilerTask.StartTime) 101 | ) 102 | 103 | let allResults = lastTasks |> List.collect (fun task -> task.Task.Result) 104 | 105 | match List.tryFind (fun result -> result.ExitCode <> 0) allResults with 106 | | Some result -> 107 | let errorText = 108 | result.Errors 109 | |> Seq.map (fun error -> error.ToString()) 110 | |> String.concat "\n" 111 | 112 | replyFailure errorText 113 | { tmpEmitterState with CustomState = [] } 114 | 115 | | None -> 116 | 117 | let projRefersMap = cache.ProjRefersMap 118 | 119 | let projLevelMap = cache.ProjLevelMap 120 | 121 | match developmentTarget with 122 | | DevelopmentTarget.Plugin plugin -> 123 | plugin.Unload() 124 | Thread.Sleep plugin.TimeDelayAfterUninstallPlugin 125 | | _ -> () 126 | 127 | commonState.CompilerTmp 128 | |> Seq.sortByDescending (fun projPath -> 129 | projLevelMap.[projPath] 130 | ) 131 | |> Seq.iter (fun projPath -> 132 | let currentCrackedFsproj = cache.ProjectMap.[projPath] 133 | 134 | CrackedFsproj.copyObjToBin Configuration.Debug currentCrackedFsproj 135 | 136 | let refCrackedFsprojs = projRefersMap.[projPath] 137 | 138 | refCrackedFsprojs |> Seq.sortByDescending (fun refCrackedFsproj -> 139 | projLevelMap.[refCrackedFsproj.ProjPath] 140 | ) 141 | |> Seq.iter (CrackedFsproj.copyFileFromRefDllToBin Configuration.Debug projPath) 142 | ) 143 | 144 | replySuccess() 145 | 146 | match developmentTarget with 147 | | DevelopmentTarget.Plugin plugin -> 148 | plugin.Load() 149 | plugin.Calculate() 150 | | _ -> () 151 | 152 | CompilerTmpEmitterState.createEmpty [] cache 153 | 154 | | _ -> tmpEmitterState 155 | 156 | let startServer workingDir (compileTmpEmitterAgent: IMailboxProcessor) = 157 | let root = 158 | File.tryFindRootUpByLaunchJson workingDir 159 | |> Option.defaultValue workingDir 160 | 161 | async { 162 | 163 | let webApp = 164 | let emitCompilerTmp: WebPart = 165 | fun (ctx : HttpContext) -> 166 | async { 167 | let! handler = 168 | /// will finnaly invoke tryEmit 169 | compileTmpEmitterAgent.PostAndAsyncReply TmpEmitterMsg 170 | return! handler ctx 171 | } 172 | 173 | choose [ 174 | path "/emitCompilerTmp" >=> emitCompilerTmp 175 | ] 176 | let config = 177 | let freePort = freePort() 178 | generateCurlCache freePort root 179 | let local = Suave.Http.HttpBinding.createSimple HTTP "127.0.0.1" freePort 180 | 181 | { defaultConfig with bindings = [local] } 182 | 183 | startWebServer config webApp 184 | } |> Async.Start 185 | 186 | let create developmentTarget = 187 | { new ICompilerTmpEmitter<_, _, CompilerResult> with 188 | member x.TryEmit(workingDir, state) = TmpEmitterState.tryEmit developmentTarget state 189 | member x.ProcessCustomMsg (customMsg, state) = 190 | let (TmpEmitterMsg replyChannel) = customMsg 191 | { state with 192 | CustomState = replyChannel :: state.CustomState } 193 | |> TmpEmitterState.tryEmit developmentTarget 194 | 195 | member x.CustomInitialState = [] } 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /tests/datas/repro-projects/src/Masse.Admin.Tools/Masse.Admin.Tools.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | 33 | PreserveNewest 34 | 35 | 36 | PreserveNewest 37 | 38 | 39 | PreserveNewest 40 | 41 | 42 | PreserveNewest 43 | 44 | 45 | PreserveNewest 46 | 47 | 48 | PreserveNewest 49 | 50 | 51 | PreserveNewest 52 | 53 | 54 | PreserveNewest 55 | 56 | 57 | 58 | PreserveNewest 59 | 60 | 61 | PreserveNewest 62 | 63 | 64 | PreserveNewest 65 | 66 | 67 | PreserveNewest 68 | 69 | 70 | PreserveNewest 71 | 72 | 73 | PreserveNewest 74 | 75 | 76 | PreserveNewest 77 | 78 | 79 | PreserveNewest 80 | 81 | 82 | PreserveNewest 83 | 84 | 85 | PreserveNewest 86 | 87 | 88 | PreserveNewest 89 | 90 | 91 | PreserveNewest 92 | 93 | 94 | PreserveNewest 95 | 96 | 97 | PreserveNewest 98 | 99 | 100 | PreserveNewest 101 | 102 | 103 | 104 | 105 | PreserveNewest 106 | 107 | 108 | PreserveNewest 109 | 110 | 111 | PreserveNewest 112 | 113 | 114 | PreserveNewest 115 | 116 | 117 | PreserveNewest 118 | 119 | 120 | PreserveNewest 121 | 122 | 123 | PreserveNewest 124 | 125 | 126 | PreserveNewest 127 | 128 | 129 | PreserveNewest 130 | 131 | 132 | PreserveNewest 133 | 134 | 135 | PreserveNewest 136 | 137 | 138 | PreserveNewest 139 | 140 | 141 | PreserveNewest 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Run standard fsharp codes in watch mode 2 | 3 | Stable | Prerelease 4 | --- | --- 5 | [![NuGet Badge](https://buildstats.info/nuget/FCSWatch.Binary)](https://www.nuget.org/packages/FCSWatch.Binary/) | [![NuGet Badge](https://buildstats.info/nuget/FCSWatch.Binary?includePreReleases=true)](https://www.nuget.org/packages/FCSWatch.Binary/) 6 | 7 | 8 | MacOS/Linux | Windows 9 | --- | --- 10 | [![CircleCI](https://circleci.com/gh/humhei/FCSWatch.svg?style=svg)](https://circleci.com/gh/humhei/FCSWatch) | [![Build status](https://ci.appveyor.com/api/projects/status/0qnls95ohaytucsi?svg=true)](https://ci.appveyor.com/project/ts2fable-imports/FCSWatch) 11 | [![Build History](https://buildstats.info/circleci/chart/humhei/FCSWatch)](https://circleci.com/gh/humhei/FCSWatch) | [![Build History](https://buildstats.info/appveyor/chart/ts2fable-imports/FCSWatch)](https://ci.appveyor.com/project/ts2fable-imports/FCSWatch) 12 | 13 | 14 | --- 15 | 16 | 17 | ## Get started 18 | ### From Cli 19 | `dotnet tool install --global fcswatch-cli` 20 | 21 | `fcswatch --project-file yourProjectFile` 22 | 23 | FcsWatch will load your project in autoReload mode by defalut 24 | 25 | ``` 26 | USAGE: fcswatch.exe [--help] [--working-dir ] [--project-file ] [--debuggable] 27 | [--logger-level ] [--no-build] [--webhook ] [--send] 28 | [--framework ] [--configuration ] 29 | 30 | OPTIONS: 31 | 32 | --working-dir 33 | Specfic working directory, default is current directory 34 | --project-file 35 | Entry project file, default is exact fsproj file in working dir 36 | --debuggable Enable debuggable in vscode, This will disable auto Reload 37 | --logger-level 38 | Default is Minimal 39 | --no-build --no-build 40 | --webhook send a web hook when program (re)run 41 | --send Equivalent to --webhook http://localhost:9867/update 42 | --framework, -f 43 | The target framework to build for. The default to prefer netcore. 44 | --configuration, -c 45 | Has limitations; See #26 for detail 46 | The configuration to use for building the project. The default is Debug. 47 | --help display this list of options. 48 | 49 | -- --your-custom-args value (pass Addtional args pass to application) 50 | ``` 51 | 52 | ### From Fake 53 | 1. Install [fake5](https://fake.build/fake-gettingstarted.html) 54 | 2. Replace build.fsx with below codes 55 | ```fsharp 56 | #r "paket: 57 | source https://api.nuget.org/v3/index.json 58 | nuget Fake.Core.Target = 5.12.0 59 | nuget FcsWatch.Binary //" 60 | #load "./.fake/build.fsx/intellisense.fsx" 61 | 62 | // start build 63 | open Fake.Core 64 | open Fake.IO 65 | open FSharp.Compiler.SourceCodeServices 66 | open FcsWatch.Binary 67 | 68 | Target.create "FcsWatch" (fun _ -> 69 | /// replace it to your entry project file 70 | let projectFile = Path.getFullName "./FcsWatchMiniSample/FcsWatchMiniSample.fsproj" 71 | runFcsWatcher Config.DefaultValue projectFile 72 | ) 73 | 74 | Target.runOrDefault "FcsWatch" 75 | 76 | ``` 77 | 3. `fake build -t "FcsWatch"` 78 | 4. `Change fs files in YourProject` and save it 79 | 80 | 81 | ## Build From project 82 | * .paket/paket.exe install 83 | * dotnet build FCSWatch.sln 84 | 85 | ## File structure 86 | ### FcsWatch.Core 87 | The core library (Include a lots of common logic 88 | e.g `project cracker`, `file watcher`, mailbox group for concurrrent, and so on ) 89 | 90 | ### FcsWatch.Binary (Ref FcsWatch.Core) 91 | It compile fsharp codes to .dll and .pdb 92 | And then stop the whole application and replace the `.dll` and `.pdb` and then rerun application 93 | 94 | ### FcsWatch-Porta (Ref FcsWatch.Core) 95 | It is ported from [FSharp.Compiler.PortaCode](https://github.com/fsprojects/FSharp.Compiler.PortaCode) 96 | It sends a webhook to the host program 97 | And then, the host program can replace its logic 98 | 99 | 100 | ## Debug in vscode(only when AutoReload is false) 101 | 102 | ### Play around 103 | #### From source code interaction test 104 | * git clone https://github.com/humhei/FCSWatch.git 105 | * fcswatch --project-file "fullPath to \FCSWatch\tests\datas\TestProject\TestProject.fsproj" --debuggable 106 | * modify fs files in any of TestProject,TestLib2,TestLib1 107 | * Set breakpoint in any of TestProject,TestLib2,TestLib1 108 | * F5 Debug `Launch TestProject` 109 | * modify fs files in any of TestProject,TestLib2,TestLib1 110 | * (Optional) add new fs file in any of TestProject,TestLib2,TestLib1 111 | * Relaunch Debugger 112 | 113 | 114 | ### Launch debugging in vscode 115 | You can also launch debugging when running in watch mode 116 | ``` 117 | { 118 | "name": "launch TestProject", 119 | "type": "coreclr", 120 | "request": "launch", 121 | "preLaunchTask": "emitCompilerTmp", 122 | "program": "${workspaceFolder}/YourProject/bin/Debug/targetFramwork/YourProject.exe", 123 | }, 124 | /// send a http request to copy dlls in obj to bin 125 | { 126 | "label": "emitCompilerTmp", 127 | "command": "curl", 128 | "args": [ 129 | "--config", 130 | ".fake/fcswatch/port.cache" 131 | ], 132 | "presentation": { 133 | "reveal": "silent" 134 | } 135 | }, 136 | }, 137 | ``` 138 | 139 | When you are debugging files,watch mode still take effect 140 | 141 | 142 | ## Plugin mode 143 | e.g.: excelDna sample 144 | vscode launch.json setting 145 | ``` 146 | { 147 | "name": "Attach Excel", 148 | "type": "clr", 149 | "request": "attach", 150 | "preLaunchTask": "emitCompilerTmp", 151 | /// should be write automatically by script 152 | "processId": 14876 153 | }, 154 | ``` 155 | 156 | build.fsx setting 157 | ```fsharp 158 | open FcsWatch.Binary 159 | 160 | let app = 161 | Process.GetProcesses() 162 | |> Array.tryFind (fun proc -> proc.ProcessName = "EXCEL") 163 | |> function 164 | | Some proc -> Marshal.GetActiveObject("Excel.Application") 165 | | None -> 166 | failwithf "Please manual open excel" projectName 167 | :?> Application 168 | 169 | let procId = User32.getPidFromHwnd app.Hwnd 170 | 171 | /// trigger when file changed was detected 172 | /// and (re)load debugger (after emit cache) 173 | let installPlugin() = 174 | addIn.Installed <- true 175 | Trace.trace "installed plugin" 176 | 177 | /// trigger when file changed was detected 178 | /// and (re)load debugger (before emit cache) 179 | let unInstall() = 180 | addIn.Installed <- false 181 | Trace.trace "unInstalled plugin" 182 | 183 | /// trigger when (re)load debugger (after installPlugin()) 184 | let calculate() = 185 | Trace.trace "calculate worksheet" 186 | worksheet.Calculate() 187 | 188 | 189 | let pluginDebugInfo: PluginDebugInfo = 190 | { 191 | /// Thread sleed to wait debugger attached. 192 | /// Trigger when file changed was not detected 193 | /// and reload debugger 194 | DebuggerAttachTimeDelay = 2000 195 | // pid write to .vscode/launch.json 196 | Pid = procId 197 | VscodeLaunchConfigurationName = "Attach Excel" } 198 | 199 | let plugin : DebuggingServer.Plugin = 200 | { Load = install 201 | Unload = unInstall 202 | Calculate = calculate 203 | TimeDelayAfterUninstallPlugin = 500 204 | PluginDebugInfo = pluginDebugInfo } 205 | 206 | let config = 207 | {Config.DefaultValue with 208 | DevelopmentTarget = DevelopmentTarget.autoReloadPlugin plugin } 209 | 210 | runFcsWatcher config projectFile 211 | 212 | ``` 213 | 214 | ## Why? 215 | why not use dotnet watch: 216 | 1. dotnet watch reference all dlls every time (which will take at least 3000ms?) (while fcs hold dlls in runtime cache) 217 | 2. not easy to debug when you are using dotnet watch 218 | 219 | 220 | ![](https://github.com/humhei/Resources/blob/Resources/TestfsFCSWatchVisualStud.gif) 221 | -------------------------------------------------------------------------------- /src/FcsWatch.Core/CompilerTmpEmitter.fs: -------------------------------------------------------------------------------- 1 | module FcsWatch.Core.CompilerTmpEmitter 2 | open Types 3 | open System.Threading 4 | 5 | 6 | [] 7 | type CompilerTmpEmitterMsg<'Result when 'Result :> ICompilerOrCheckResult> = 8 | | IncrCompilingNum of int * replyChannel: AsyncReplyChannel 9 | | DecrCompilingNum of int 10 | | AddTmp of string (*proj path*) 11 | | AddTask of CompilerTask<'Result> 12 | | UpdateCache of CrackedFsprojBundleCache 13 | /// for blackbox test 14 | | WaitCompiled of AsyncReplyChannel 15 | 16 | [] 17 | type CompilerTmpEmitterMsg<'CustomMsg, 'Result when 'Result :> ICompilerOrCheckResult> = 18 | | CustomMsg of 'CustomMsg 19 | | CommonMsg of CompilerTmpEmitterMsg<'Result> 20 | 21 | [] 22 | module CompilerTmpEmitterMsg = 23 | let addTmp projPath = 24 | (CompilerTmpEmitterMsg<_, _>.CommonMsg (CompilerTmpEmitterMsg<_>.AddTmp projPath)) 25 | 26 | let incrCompilingNum (number, replyChannel) = 27 | (CompilerTmpEmitterMsg<_, _>.CommonMsg (CompilerTmpEmitterMsg<_>.IncrCompilingNum (number, replyChannel))) 28 | 29 | let decrCompilingNum number = 30 | (CompilerTmpEmitterMsg<_, _>.CommonMsg (CompilerTmpEmitterMsg<_>.DecrCompilingNum number)) 31 | 32 | let addTask task = 33 | (CompilerTmpEmitterMsg<_, _>.CommonMsg (CompilerTmpEmitterMsg<_>.AddTask task)) 34 | 35 | let updateCache cache = 36 | (CompilerTmpEmitterMsg<_, _>.CommonMsg (CompilerTmpEmitterMsg<_>.UpdateCache cache)) 37 | 38 | let internal customMsg msg = 39 | (CompilerTmpEmitterMsg<_, _>.CustomMsg msg) 40 | 41 | 42 | /// for blackbox test 43 | let internal waitCompiled replyChannel = 44 | (CompilerTmpEmitterMsg<_, _>.CommonMsg (CompilerTmpEmitterMsg<_>.WaitCompiled replyChannel)) 45 | 46 | let msgName = function 47 | | CompilerTmpEmitterMsg.IncrCompilingNum _ -> "IncrCompilingNum" 48 | | CompilerTmpEmitterMsg.DecrCompilingNum _ -> "DecrCompilingNum" 49 | | CompilerTmpEmitterMsg.AddTmp _ -> "AddTmp" 50 | | CompilerTmpEmitterMsg.AddTask _ -> "AddTask" 51 | | CompilerTmpEmitterMsg.UpdateCache _ -> "UpdateCache" 52 | | CompilerTmpEmitterMsg.WaitCompiled _ -> "WaitCompiled" 53 | 54 | type CompilerTmpEmitterState<'Result when 'Result :> ICompilerOrCheckResult> = 55 | { CompilingNumber: int 56 | /// proj paths 57 | CompilerTmp: Set 58 | CachedCompilerTasks: CompilerTask<'Result> list 59 | /// used for test only 60 | LastestIncredNum: int 61 | CrackerFsprojFileBundleCache: CrackedFsprojBundleCache 62 | AccumWaitCompiledReplyChannels: AsyncReplyChannel list} 63 | 64 | type CompilerTmpEmitterState<'CustomState, 'Result when 'Result :> ICompilerOrCheckResult> = 65 | { CustomState: 'CustomState 66 | CommonState: CompilerTmpEmitterState<'Result> } 67 | 68 | [] 69 | module CompilerTmpEmitterState = 70 | 71 | let createCommonEmpty cache = 72 | { CompilingNumber = 0 73 | CompilerTmp = Set.empty 74 | CachedCompilerTasks = [] 75 | LastestIncredNum = 0 76 | CrackerFsprojFileBundleCache = cache 77 | AccumWaitCompiledReplyChannels = [] } 78 | 79 | let createEmpty customState cache = 80 | { CommonState = createCommonEmpty cache 81 | CustomState = customState } 82 | 83 | 84 | let setCompilingNumber number state = 85 | { state with 86 | CommonState = 87 | {state.CommonState with CompilingNumber = number }} 88 | 89 | 90 | let internal tryReplyCompiled (state: CompilerTmpEmitterState<_>) = 91 | if state.CompilingNumber = 0 && state.AccumWaitCompiledReplyChannels.Length > 0 92 | then 93 | state.AccumWaitCompiledReplyChannels |> List.iter (fun replyChannel -> replyChannel.Reply state.LastestIncredNum) 94 | createCommonEmpty state.CrackerFsprojFileBundleCache 95 | else 96 | state 97 | 98 | let processMsg (msg: CompilerTmpEmitterMsg<_>) (state: CompilerTmpEmitterState<_>) = 99 | let newState = 100 | match msg with 101 | 102 | | CompilerTmpEmitterMsg.DecrCompilingNum number -> 103 | let compilingNumber = state.CompilingNumber - number 104 | let newState = {state with CompilingNumber = compilingNumber} 105 | 106 | assert (newState.CompilingNumber >= 0) 107 | newState 108 | 109 | | CompilerTmpEmitterMsg.IncrCompilingNum (number, replyChannel) -> 110 | let compilingNumber = state.CompilingNumber + number 111 | let newState = 112 | {state with 113 | CompilingNumber = compilingNumber 114 | LastestIncredNum = compilingNumber } 115 | 116 | replyChannel.Reply() 117 | 118 | newState 119 | 120 | | CompilerTmpEmitterMsg.AddTmp projectFile -> 121 | let newCompilerTmp = state.CompilerTmp.Add projectFile 122 | 123 | {state with CompilerTmp = newCompilerTmp } 124 | 125 | | CompilerTmpEmitterMsg.AddTask task -> 126 | {state with CachedCompilerTasks = task :: state.CachedCompilerTasks} 127 | 128 | | CompilerTmpEmitterMsg.UpdateCache cache -> 129 | {state with CrackerFsprojFileBundleCache = cache} 130 | 131 | | CompilerTmpEmitterMsg.WaitCompiled replyChannel -> 132 | let newState = { state with AccumWaitCompiledReplyChannels = replyChannel :: state.AccumWaitCompiledReplyChannels } 133 | tryReplyCompiled newState 134 | 135 | let msgName = CompilerTmpEmitterMsg.msgName msg 136 | logger.Info "compilerTmpEmitter agent receive message %s,current compiling number is %d" msgName newState.CompilingNumber 137 | 138 | newState 139 | 140 | type ICompilerTmpEmitter<'CustomMsg, 'CustomState,'Result when 'Result :> ICompilerOrCheckResult> = 141 | abstract member ProcessCustomMsg: customMsg: 'CustomMsg * state: CompilerTmpEmitterState<'CustomState,'Result> -> CompilerTmpEmitterState<'CustomState,'Result> 142 | abstract member TryEmit: workingDir: string * state: CompilerTmpEmitterState<'CustomState,'Result> -> CompilerTmpEmitterState<'CustomState,'Result> 143 | abstract member CustomInitialState: 'CustomState 144 | 145 | let compilerTmpEmitterAgent workingDir (compilerTmpEmitter: ICompilerTmpEmitter<_, _, _>) (initialCache: CrackedFsprojBundleCache) = MailboxProcessor>.Start(fun inbox -> 146 | inbox.Error.Add(fun error -> logger.Error "%A" error) 147 | 148 | let rec loop state = async { 149 | 150 | let! msg = inbox.Receive() 151 | match msg with 152 | | CompilerTmpEmitterMsg.CustomMsg customMsg -> 153 | let newState = compilerTmpEmitter.ProcessCustomMsg (customMsg, state) 154 | return! loop newState 155 | | CompilerTmpEmitterMsg.CommonMsg commonMsg -> 156 | 157 | let commonProcessedState = 158 | { state with 159 | CommonState = CompilerTmpEmitterState.processMsg commonMsg state.CommonState } 160 | 161 | match commonMsg with 162 | | CompilerTmpEmitterMsg.DecrCompilingNum _ -> 163 | 164 | let resetWaitCompiled state = 165 | { state with 166 | CommonState = 167 | { state.CommonState 168 | with 169 | LastestIncredNum = commonProcessedState.CommonState.LastestIncredNum 170 | AccumWaitCompiledReplyChannels = commonProcessedState.CommonState.AccumWaitCompiledReplyChannels } } 171 | 172 | let replyWaitCompiled state = 173 | { state with 174 | CommonState = CompilerTmpEmitterState.tryReplyCompiled state.CommonState } 175 | 176 | let newState = 177 | compilerTmpEmitter.TryEmit(workingDir, commonProcessedState) 178 | |> resetWaitCompiled 179 | |> replyWaitCompiled 180 | 181 | 182 | return! loop newState 183 | 184 | | _ -> return! loop commonProcessedState 185 | } 186 | loop (CompilerTmpEmitterState.createEmpty compilerTmpEmitter.CustomInitialState initialCache) 187 | ) 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /src/FcsWatch.Binary/AutoReload.fs: -------------------------------------------------------------------------------- 1 | 2 | namespace FcsWatch.Binary 3 | open Fake.Core 4 | open FcsWatch.Core 5 | open Types 6 | open FcsWatch.Core.CrackedFsproj 7 | open System.Diagnostics 8 | open System.Collections.Concurrent 9 | open Fake.DotNet 10 | open FcsWatch.Binary.Helpers 11 | open Fake.IO 12 | open FcsWatch.Core.CompilerTmpEmitter 13 | open Extensions 14 | open System.Threading 15 | open System.Net 16 | open System.Text 17 | 18 | 19 | [] 20 | type WhyRun = 21 | | Rerun of dllUpdates: string list 22 | | Run 23 | 24 | type PluginDebugInfo = 25 | { DebuggerAttachTimeDelay: int 26 | Pid: int 27 | VscodeLaunchConfigurationName: string } 28 | 29 | [] 30 | module PluginDebugInfo = 31 | open VscodeHelper 32 | 33 | 34 | let writePidForPlugin workingDir (pluginDebugInfo: PluginDebugInfo) = 35 | match File.tryFindLaunchJsonUp workingDir with 36 | | Some file -> 37 | 38 | let launch = Launch.read file 39 | Launch.writePid pluginDebugInfo.VscodeLaunchConfigurationName pluginDebugInfo.Pid launch 40 | |> Launch.write file 41 | | None -> logger.Important "Doesn't exists .vscode/launch.json, If you want write attachable pid automatically to launch.json, Please write it first" 42 | 43 | 44 | 45 | [] 46 | module AutoReload = 47 | 48 | type TmpEmitterMsg = unit 49 | type TmpEmitterState = CompilerTmpEmitterState 50 | 51 | 52 | 53 | type Plugin = 54 | { Load: unit -> unit 55 | Unload: unit -> unit 56 | Calculate: unit -> unit 57 | TimeDelayAfterUninstallPlugin: int 58 | /// sometimes i want work both in autoReload + debuggable 59 | PluginDebugInfo: PluginDebugInfo option } 60 | 61 | 62 | [] 63 | type DevelopmentTarget = 64 | | Program 65 | | Plugin of Plugin 66 | 67 | type ProgramRunningArgs = 68 | { Webhook: string option 69 | AdditionalBuildArgs: string list 70 | AdditionalSwitchArgs: string list 71 | DevelopmentTarget: DevelopmentTarget 72 | WorkingDir: string 73 | Framework: string option 74 | Configuration: Configuration 75 | WhyRun: WhyRun } 76 | 77 | [] 78 | module ProgramRunningArgs = 79 | 80 | let addtionalRunArgs (crackedFsproj: CrackedFsproj) (args: ProgramRunningArgs) = 81 | let framework = 82 | match args.Framework with 83 | | Some f -> ["--framework"; f] 84 | | None -> ["--framework"; crackedFsproj.PreferFramework] 85 | 86 | if args.AdditionalSwitchArgs.Length = 0 87 | then 88 | args.AdditionalBuildArgs @ framework 89 | else 90 | args.AdditionalBuildArgs @ framework @ ["--"] @ args.AdditionalSwitchArgs 91 | 92 | 93 | let private runningProjects = new ConcurrentDictionary() 94 | 95 | [] 96 | module internal CrackedFsproj = 97 | 98 | let private dotnetTool = 99 | [DotNet.Options.Create().DotNetCliPath] 100 | |> Args.toWindowsCommandLine 101 | 102 | [] 103 | module Process = 104 | let start tool args workingDir = 105 | let startInfo = new ProcessStartInfo(); 106 | let args = Args.toWindowsCommandLine args 107 | startInfo.WorkingDirectory <- workingDir 108 | startInfo.Arguments <- args 109 | startInfo.FileName <- tool 110 | logger.ImportantGreen "%s %s" tool args 111 | Process.Start(startInfo) 112 | 113 | let tryRun (args: ProgramRunningArgs) (crackedFsproj: CrackedFsproj) = 114 | 115 | match (args.DevelopmentTarget: DevelopmentTarget) with 116 | | DevelopmentTarget.Program -> 117 | match crackedFsproj.ProjectTarget with 118 | | ProjectTarget.Exe -> 119 | 120 | let dotnetArgs = 121 | ["run";"--project"; crackedFsproj.ProjPath; "--no-build"; "--no-restore"] 122 | 123 | 124 | runningProjects.GetOrAdd (crackedFsproj.ProjPath,fun _ -> 125 | let proc = 126 | Process.start 127 | dotnetTool 128 | (dotnetArgs @ (ProgramRunningArgs.addtionalRunArgs crackedFsproj args)) 129 | args.WorkingDir 130 | 131 | match args.Webhook with 132 | | Some webhook -> 133 | let json = Newtonsoft.Json.JsonConvert.SerializeObject args.WhyRun 134 | use webClient = new WebClient(Encoding = Encoding.UTF8) 135 | logger.Important "SENDING TO WEBHOOK... " // : <<<%s>>>... --> %s" json.[0 .. min (json.Length - 1) 100] hook 136 | let resp = webClient.UploadString (webhook,"Put",json) 137 | logger.Important "RESP FROM WEBHOOK: %s" resp 138 | 139 | | None -> () 140 | 141 | proc 142 | 143 | 144 | ) 145 | |> ignore 146 | 147 | | ProjectTarget.Library -> 148 | failwith "project is a library, autoReload is ture, development target is program;" 149 | 150 | | DevelopmentTarget.Plugin plugin -> 151 | plugin.Load() 152 | 153 | let tryReRun args (crackedFsproj: CrackedFsproj) (dllUpdates: string list) = 154 | let args = { args with WhyRun = WhyRun.Rerun dllUpdates } 155 | tryRun args (crackedFsproj: CrackedFsproj) 156 | 157 | 158 | let tryKill developmentTarget (crackedFsproj: CrackedFsproj) = 159 | match developmentTarget with 160 | | DevelopmentTarget.Plugin plugin -> 161 | plugin.Unload() 162 | Thread.Sleep plugin.TimeDelayAfterUninstallPlugin 163 | | DevelopmentTarget.Program -> 164 | match runningProjects.TryRemove (crackedFsproj.ProjPath) with 165 | | true, proc -> 166 | if not proc.HasExited 167 | then 168 | proc.KillTree() 169 | logger.InfoGreen "Killed process %d" proc.Id 170 | | false, _ -> 171 | failwithf "Cannot remove %s from running projects" crackedFsproj.ProjPath 172 | 173 | 174 | [] 175 | module internal TmpEmitterState = 176 | 177 | let tryEmit args (state: TmpEmitterState) = 178 | let commonState = state.CommonState 179 | let cache = commonState.CrackerFsprojFileBundleCache 180 | 181 | match commonState.CompilingNumber with 182 | | 0 -> 183 | 184 | logger.Info "Current cached compier task is %d" commonState.CachedCompilerTasks.Length 185 | 186 | match commonState.CachedCompilerTasks with 187 | | [task] when task.Why = WhyCompile.WarmCompile -> state 188 | | _ -> 189 | let lastTasks = 190 | commonState.CachedCompilerTasks 191 | |> List.groupBy (fun compilerTask -> 192 | compilerTask.Task.Result.[0].ProjPath 193 | ) 194 | |> List.map (fun (projPath, compilerTasks) -> 195 | compilerTasks |> List.maxBy (fun compilerTask -> compilerTask.StartTime) 196 | ) 197 | 198 | 199 | let allResults: CompilerResult list = lastTasks |> List.collect (fun task -> task.Task.Result) 200 | 201 | match List.tryFind (fun (result: CompilerResult) -> result.ExitCode <> 0) allResults with 202 | | Some result -> state 203 | 204 | | None -> 205 | 206 | let projRefersMap = cache.ProjRefersMap 207 | 208 | let projLevelMap = cache.ProjLevelMap 209 | 210 | CrackedFsproj.tryKill args.DevelopmentTarget cache.EntryCrackedFsproj 211 | 212 | commonState.CompilerTmp 213 | |> Seq.sortByDescending (fun projPath -> 214 | projLevelMap.[projPath] 215 | ) 216 | |> Seq.iter (fun projPath -> 217 | let currentCrackedFsproj = cache.ProjectMap.[projPath] 218 | 219 | CrackedFsproj.copyObjToBin args.Configuration currentCrackedFsproj 220 | 221 | let refCrackedFsprojs = projRefersMap.[projPath] 222 | 223 | refCrackedFsprojs |> Seq.sortByDescending (fun refCrackedFsproj -> 224 | projLevelMap.[refCrackedFsproj.ProjPath] 225 | ) 226 | |> Seq.iter (CrackedFsproj.copyFileFromRefDllToBin args.Configuration projPath) 227 | 228 | 229 | ) 230 | 231 | let updatedDlls = allResults |> List.map (fun r -> r.Dll) 232 | CrackedFsproj.tryReRun args cache.EntryCrackedFsproj updatedDlls 233 | 234 | CompilerTmpEmitterState.createEmpty 0 cache 235 | | _ -> 236 | state 237 | 238 | 239 | let create args = 240 | { new ICompilerTmpEmitter with 241 | member x.TryEmit(_, state) = TmpEmitterState.tryEmit args state 242 | member x.ProcessCustomMsg (_, state) = state 243 | member x.CustomInitialState = 0 } 244 | -------------------------------------------------------------------------------- /tests/FcsWatch.Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module FcsWatch.Tests.Tests 2 | open Expecto 3 | open System.IO 4 | open Fake.IO 5 | open Fake.IO.FileSystemOperators 6 | open System.Threading 7 | open FcsWatch.Core.Types 8 | open FcsWatch.Core.FcsWatcher 9 | open FcsWatch.Tests.Types 10 | open FcsWatch.Core 11 | open FcsWatch.Binary 12 | open Suave 13 | open Suave.Operators 14 | open Suave.Filters 15 | 16 | let pass() = Expect.isTrue true "passed" 17 | let fail() = Expect.isTrue false "failed" 18 | 19 | let root = __SOURCE_DIRECTORY__ "../../" 20 | 21 | let datas = Path.getDirectory(__SOURCE_DIRECTORY__) "datas" 22 | 23 | let entryProjDir = datas "TestProject" 24 | 25 | let entryProjPath = entryProjDir "TestProject.fsproj" 26 | 27 | let testProjPath = datas @"TestLib2/TestLib2.fsproj" 28 | 29 | let testSourceFile1 = datas @"TestLib2/Library.fs" 30 | 31 | let testSourceFile2 = datas @"TestLib2/Library2.fs" 32 | 33 | let testSourceFileAdded = datas @"TestLib2/Added.fs" 34 | 35 | let testSourceFile1InTestLib = datas @"TestLib1/Library.fs" 36 | 37 | let testContentFile = datas @"TestProject/Content.html" 38 | 39 | let expectCompilerNumber excepted (CompilerNumber compilerNumber) = 40 | Expect.equal excepted compilerNumber (sprintf "expected compiler number %d,while current compiler number is %d" excepted compilerNumber) 41 | 42 | 43 | let makeFileChange fullPath : FileChange = 44 | let fullPath = Path.getFullName fullPath 45 | 46 | { FullPath = fullPath 47 | Name = Path.GetFileName fullPath 48 | Status = FileStatus.Changed } 49 | 50 | let makeSourceFileChanges fullPaths = 51 | FcsWatcherMsg.DetectSourceFileChanges (List.map makeFileChange fullPaths) 52 | 53 | let makeProjectFileChanges fullPaths = 54 | FcsWatcherMsg.DetectProjectFileChanges (List.map makeFileChange fullPaths) 55 | 56 | 57 | let createWatcher (config: BinaryConfig) = 58 | lazy 59 | let config = 60 | { config with 61 | WorkingDir = root 62 | LoggerLevel = Logger.Level.Debug 63 | WarmCompile = false } 64 | 65 | binaryFcsWatcher config entryProjPath 66 | 67 | let testSourceFilesChanged (watcher: Lazy>) sourceFiles expectedCompilerNumber = 68 | watcher.Value.PostAndReply (makeSourceFileChanges sourceFiles) 69 | watcher.Value.PostAndReply FcsWatcherMsg.WaitCompiled 70 | |> expectCompilerNumber expectedCompilerNumber 71 | 72 | let programTests = 73 | let watcher = createWatcher { BinaryConfig.DefaultValue with DevelopmentTarget = DevelopmentTarget.debuggableProgram } 74 | 75 | testList "program tests" [ 76 | 77 | testCase "change file in TestLib2/Library.fs will trigger compiling" <| fun _ -> 78 | /// TestLib2/Library.fs 79 | testSourceFilesChanged watcher [testSourceFile1] 1 80 | 81 | testCase "change multiple file in TestLib2 will trigger compiling" <| fun _ -> 82 | /// (TestLib2/Library.fs + TestLib2/Library2.fs) 83 | testSourceFilesChanged watcher [testSourceFile1; testSourceFile2] 1 84 | 85 | testCase "change file in TestLib1 and TestLib2 will trigger compiling" <| fun _ -> 86 | /// (TestLib2/*.fs) + (TestLib1/*.fs) 87 | testSourceFilesChanged watcher [testSourceFile1; testSourceFile2; testSourceFile1InTestLib] 2 88 | 89 | testCase "add fs file in fsproj will update watcher" <| fun _ -> 90 | try 91 | Fsproj.addFileToProject "Added.fs" testProjPath 92 | 93 | Thread.Sleep(1000) 94 | testSourceFilesChanged watcher [testSourceFileAdded] 1 95 | 96 | finally 97 | Fsproj.removeFileFromProject "Added.fs" testProjPath 98 | 99 | testCase "change html content file in TestProject will trigger compiling" <| fun _ -> 100 | /// TestProject/Content.html 101 | testSourceFilesChanged watcher [testContentFile] 1 102 | ] 103 | 104 | 105 | let pluginTests = 106 | let watcher = 107 | let config = 108 | let installPlugin() = printfn "install plugin" 109 | 110 | let unInstallPlugin() = printfn "uninstall plugin" 111 | 112 | let plugin: AutoReload.Plugin = 113 | { Load = installPlugin 114 | Unload = unInstallPlugin 115 | Calculate = (fun _ -> 116 | Thread.Sleep(100) 117 | printf "Calculate" ) 118 | TimeDelayAfterUninstallPlugin = 500 119 | PluginDebugInfo = None } 120 | 121 | {BinaryConfig.DefaultValue with DevelopmentTarget = DevelopmentTarget.autoReloadPlugin plugin } 122 | 123 | createWatcher config 124 | 125 | 126 | testList "plugin Tests" [ 127 | testCase "in plugin mode " <| fun _ -> 128 | /// TestLib2/Library.fs 129 | testSourceFilesChanged watcher [testSourceFile1] 1 130 | ] 131 | 132 | 133 | 134 | let webhookTests = 135 | let watcher = 136 | createWatcher { 137 | BinaryConfig.DefaultValue with 138 | DevelopmentTarget = DevelopmentTarget.autoReloadProgram 139 | Configuration = Configuration.Release 140 | 141 | Webhook = Some "http://localhost:9867/update" } 142 | 143 | 144 | 145 | 146 | testList "webhook tests" [ 147 | testCase "can send webhook and recieve it " <| fun _ -> 148 | let cts = new CancellationTokenSource() 149 | let suaveConfig = 150 | { defaultConfig with 151 | bindings = [ HttpBinding.createSimple HTTP "127.0.0.1" 9867 ] 152 | bufferSize = 2048 153 | cancellationToken = cts.Token } 154 | 155 | let mutable runReasons = [] 156 | 157 | let webApp = 158 | choose 159 | [ path "/update" >=> 160 | (fun ctx -> 161 | let whyRun = 162 | Newtonsoft.Json.JsonConvert.DeserializeObject( 163 | ctx.request.rawForm 164 | |> System.Text.ASCIIEncoding.UTF8.GetString) 165 | runReasons <- runReasons @ [whyRun] 166 | Successful.OK "recieved web hook" ctx 167 | ) 168 | ] 169 | 170 | let listening, server = startWebServerAsync suaveConfig webApp 171 | Async.Start server 172 | 173 | /// TestLib2/Library.fs 174 | testSourceFilesChanged watcher [testSourceFile1] 1 175 | 176 | let cache = watcher.Value.PostAndReply FcsWatcherMsg.GetCache 177 | 178 | cts.Cancel() 179 | tryKill AutoReload.DevelopmentTarget.Program cache.EntryCrackedFsproj 180 | 181 | match runReasons with 182 | | [runReason; rerunReason] -> 183 | 184 | match runReason, rerunReason with 185 | | WhyRun.Run, WhyRun.Rerun [file] -> 186 | Expect.isTrue (file.EndsWith("TestLib2.dll")) "can receive list of updated dlls" 187 | 188 | | _ -> failwith "expect (run + rerun) when recieve webhook" 189 | 190 | | _ -> failwith "expect (run + rerun) when recieve webhook" 191 | 192 | 193 | ] 194 | 195 | let functionTests = 196 | 197 | let testObjRefOnly configuration = async { 198 | let! fullCracekdFsproj, _ = 199 | FullCrackedFsproj.create (FullCrackedFsprojBuilder.Project {OtherFlags = [||]; File = entryProjPath; Configuration = configuration}) 200 | 201 | let otherOptions = 202 | fullCracekdFsproj.Value.AsList |> Seq.collect (fun singleTargetCrackedFsproj -> 203 | singleTargetCrackedFsproj.FSharpProjectOptions.OtherOptions 204 | ) |> List.ofSeq 205 | 206 | let configurationText = Configuration.name configuration 207 | 208 | let p1 = 209 | otherOptions 210 | |> Seq.exists (fun option -> 211 | option.Contains (sprintf @"\bin\%s\" configurationText) 212 | || option.Contains (sprintf @"/bin/%s/" configurationText) 213 | ) 214 | |> not 215 | 216 | let p2 = 217 | otherOptions 218 | |> Seq.exists (fun option -> 219 | option.Contains (sprintf @"\obj\%s\" configurationText) 220 | || option.Contains (sprintf @"/obj/%s/" configurationText) 221 | ) 222 | 223 | if p1 && p2 then pass() 224 | else fail() 225 | } 226 | 227 | 228 | testList "functionTests" 229 | [ 230 | /// "bin ref may be locked by program 231 | testCaseAsync "obj ref only when debug" <| (testObjRefOnly Configuration.Debug) 232 | 233 | /// "bin ref may be locked by program 234 | testCaseAsync "obj ref only when release" <| (testObjRefOnly Configuration.Release) 235 | 236 | testCase "EasyGetAllFsProjects for complex projects" <| fun _ -> 237 | /// no exception should be threw in unix 238 | /// https://github.com/humhei/FCSWatch/issues/19 239 | FullCrackedFsproj.easyGetAllProjPaths (datas "repro-projects\src\Masse.API\Masse.API.fsproj") 240 | |> ignore 241 | 242 | /// https://github.com/humhei/FCSWatch/issues/30 243 | testCaseAsync "Map project Other options --doc:path to --doc:fullPath and -o:path to -o:fullPath" <| async { 244 | let! fullCracekdFsproj, _ = 245 | FullCrackedFsproj.create (FullCrackedFsprojBuilder.Project {OtherFlags = [||]; File = entryProjPath; Configuration = Configuration.Debug}) 246 | 247 | let otherOptions = 248 | fullCracekdFsproj.Value.AsList |> Seq.collect (fun singleTargetCrackedFsproj -> 249 | singleTargetCrackedFsproj.FSharpProjectOptions.OtherOptions 250 | ) |> List.ofSeq 251 | 252 | let testByPrefix (prefix: string) = 253 | Expect.all otherOptions (fun ops -> 254 | let index = ops.IndexOf prefix 255 | if index <> -1 then 256 | let path = ops.Substring(index + prefix.Length) 257 | Path.IsPathRooted path 258 | else true 259 | ) "pass" 260 | 261 | testByPrefix "--doc:" 262 | testByPrefix "-o:" 263 | testByPrefix "-output:" 264 | } 265 | 266 | ] -------------------------------------------------------------------------------- /FCSWatch.FPublisher.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.489 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3D12606D-9919-4874-A10C-E37F1CB84CBC}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{43FBDDAF-4E0A-4D82-BCB2-002472E7F170}" 9 | EndProject 10 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FcsWatch.Tests", "tests\FcsWatch.Tests\FcsWatch.Tests.fsproj", "{14A80081-D97A-405D-9A49-C686ABF00842}" 11 | EndProject 12 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "fcswatch-cli", "src\fcswatch-cli\fcswatch-cli.fsproj", "{BB0051D2-8692-449E-B20F-6DBD605C7553}" 13 | EndProject 14 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FcsWatch.Binary", "src\FcsWatch.Binary\FcsWatch.Binary.fsproj", "{1661E77C-1DD4-4920-9345-C86D04B37DD6}" 15 | EndProject 16 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FcsWatch.Core", "src\FcsWatch.Core\FcsWatch.Core.fsproj", "{70B2741D-D9AC-47B2-9D0C-78478562C6D5}" 17 | EndProject 18 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FsLive.Porta.Cli.Tests", "tests\FsLive.Porta.Cli.Tests\FsLive.Porta.Cli.Tests.fsproj", "{A9F69171-7544-4029-A2EA-4E6FF49D2E1C}" 19 | EndProject 20 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FcsWatch.Porta", "src\FcsWatch.Porta\FcsWatch.Porta.fsproj", "{0DDF9662-A5E5-41B2-ADF2-D38447312611}" 21 | EndProject 22 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FcsWatch.Porta.Interpreter", "src\FcsWatch.Porta.Interpreter\FcsWatch.Porta.Interpreter.fsproj", "{EC522CB8-5E90-4F4E-9059-62DAB763A544}" 23 | EndProject 24 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "fslive-cli", "src\fslive-cli\fslive-cli.fsproj", "{47CDCE8F-BF6F-4FFB-8606-FC7835C68350}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Debug|x64 = Debug|x64 30 | Debug|x86 = Debug|x86 31 | Release|Any CPU = Release|Any CPU 32 | Release|x64 = Release|x64 33 | Release|x86 = Release|x86 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {14A80081-D97A-405D-9A49-C686ABF00842}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {14A80081-D97A-405D-9A49-C686ABF00842}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {14A80081-D97A-405D-9A49-C686ABF00842}.Debug|x64.ActiveCfg = Debug|Any CPU 39 | {14A80081-D97A-405D-9A49-C686ABF00842}.Debug|x64.Build.0 = Debug|Any CPU 40 | {14A80081-D97A-405D-9A49-C686ABF00842}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {14A80081-D97A-405D-9A49-C686ABF00842}.Debug|x86.Build.0 = Debug|Any CPU 42 | {14A80081-D97A-405D-9A49-C686ABF00842}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {14A80081-D97A-405D-9A49-C686ABF00842}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {14A80081-D97A-405D-9A49-C686ABF00842}.Release|x64.ActiveCfg = Release|Any CPU 45 | {14A80081-D97A-405D-9A49-C686ABF00842}.Release|x64.Build.0 = Release|Any CPU 46 | {14A80081-D97A-405D-9A49-C686ABF00842}.Release|x86.ActiveCfg = Release|Any CPU 47 | {14A80081-D97A-405D-9A49-C686ABF00842}.Release|x86.Build.0 = Release|Any CPU 48 | {BB0051D2-8692-449E-B20F-6DBD605C7553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {BB0051D2-8692-449E-B20F-6DBD605C7553}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {BB0051D2-8692-449E-B20F-6DBD605C7553}.Debug|x64.ActiveCfg = Debug|Any CPU 51 | {BB0051D2-8692-449E-B20F-6DBD605C7553}.Debug|x64.Build.0 = Debug|Any CPU 52 | {BB0051D2-8692-449E-B20F-6DBD605C7553}.Debug|x86.ActiveCfg = Debug|Any CPU 53 | {BB0051D2-8692-449E-B20F-6DBD605C7553}.Debug|x86.Build.0 = Debug|Any CPU 54 | {BB0051D2-8692-449E-B20F-6DBD605C7553}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {BB0051D2-8692-449E-B20F-6DBD605C7553}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {BB0051D2-8692-449E-B20F-6DBD605C7553}.Release|x64.ActiveCfg = Release|Any CPU 57 | {BB0051D2-8692-449E-B20F-6DBD605C7553}.Release|x64.Build.0 = Release|Any CPU 58 | {BB0051D2-8692-449E-B20F-6DBD605C7553}.Release|x86.ActiveCfg = Release|Any CPU 59 | {BB0051D2-8692-449E-B20F-6DBD605C7553}.Release|x86.Build.0 = Release|Any CPU 60 | {1661E77C-1DD4-4920-9345-C86D04B37DD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {1661E77C-1DD4-4920-9345-C86D04B37DD6}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {1661E77C-1DD4-4920-9345-C86D04B37DD6}.Debug|x64.ActiveCfg = Debug|Any CPU 63 | {1661E77C-1DD4-4920-9345-C86D04B37DD6}.Debug|x64.Build.0 = Debug|Any CPU 64 | {1661E77C-1DD4-4920-9345-C86D04B37DD6}.Debug|x86.ActiveCfg = Debug|Any CPU 65 | {1661E77C-1DD4-4920-9345-C86D04B37DD6}.Debug|x86.Build.0 = Debug|Any CPU 66 | {1661E77C-1DD4-4920-9345-C86D04B37DD6}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {1661E77C-1DD4-4920-9345-C86D04B37DD6}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {1661E77C-1DD4-4920-9345-C86D04B37DD6}.Release|x64.ActiveCfg = Release|Any CPU 69 | {1661E77C-1DD4-4920-9345-C86D04B37DD6}.Release|x64.Build.0 = Release|Any CPU 70 | {1661E77C-1DD4-4920-9345-C86D04B37DD6}.Release|x86.ActiveCfg = Release|Any CPU 71 | {1661E77C-1DD4-4920-9345-C86D04B37DD6}.Release|x86.Build.0 = Release|Any CPU 72 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 73 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5}.Debug|Any CPU.Build.0 = Debug|Any CPU 74 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5}.Debug|x64.ActiveCfg = Debug|Any CPU 75 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5}.Debug|x64.Build.0 = Debug|Any CPU 76 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5}.Debug|x86.ActiveCfg = Debug|Any CPU 77 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5}.Debug|x86.Build.0 = Debug|Any CPU 78 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5}.Release|Any CPU.ActiveCfg = Release|Any CPU 79 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5}.Release|Any CPU.Build.0 = Release|Any CPU 80 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5}.Release|x64.ActiveCfg = Release|Any CPU 81 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5}.Release|x64.Build.0 = Release|Any CPU 82 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5}.Release|x86.ActiveCfg = Release|Any CPU 83 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5}.Release|x86.Build.0 = Release|Any CPU 84 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 85 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C}.Debug|Any CPU.Build.0 = Debug|Any CPU 86 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C}.Debug|x64.ActiveCfg = Debug|Any CPU 87 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C}.Debug|x64.Build.0 = Debug|Any CPU 88 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C}.Debug|x86.ActiveCfg = Debug|Any CPU 89 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C}.Debug|x86.Build.0 = Debug|Any CPU 90 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C}.Release|Any CPU.ActiveCfg = Release|Any CPU 91 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C}.Release|Any CPU.Build.0 = Release|Any CPU 92 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C}.Release|x64.ActiveCfg = Release|Any CPU 93 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C}.Release|x64.Build.0 = Release|Any CPU 94 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C}.Release|x86.ActiveCfg = Release|Any CPU 95 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C}.Release|x86.Build.0 = Release|Any CPU 96 | {0DDF9662-A5E5-41B2-ADF2-D38447312611}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 97 | {0DDF9662-A5E5-41B2-ADF2-D38447312611}.Debug|Any CPU.Build.0 = Debug|Any CPU 98 | {0DDF9662-A5E5-41B2-ADF2-D38447312611}.Debug|x64.ActiveCfg = Debug|Any CPU 99 | {0DDF9662-A5E5-41B2-ADF2-D38447312611}.Debug|x64.Build.0 = Debug|Any CPU 100 | {0DDF9662-A5E5-41B2-ADF2-D38447312611}.Debug|x86.ActiveCfg = Debug|Any CPU 101 | {0DDF9662-A5E5-41B2-ADF2-D38447312611}.Debug|x86.Build.0 = Debug|Any CPU 102 | {0DDF9662-A5E5-41B2-ADF2-D38447312611}.Release|Any CPU.ActiveCfg = Release|Any CPU 103 | {0DDF9662-A5E5-41B2-ADF2-D38447312611}.Release|Any CPU.Build.0 = Release|Any CPU 104 | {0DDF9662-A5E5-41B2-ADF2-D38447312611}.Release|x64.ActiveCfg = Release|Any CPU 105 | {0DDF9662-A5E5-41B2-ADF2-D38447312611}.Release|x64.Build.0 = Release|Any CPU 106 | {0DDF9662-A5E5-41B2-ADF2-D38447312611}.Release|x86.ActiveCfg = Release|Any CPU 107 | {0DDF9662-A5E5-41B2-ADF2-D38447312611}.Release|x86.Build.0 = Release|Any CPU 108 | {EC522CB8-5E90-4F4E-9059-62DAB763A544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 109 | {EC522CB8-5E90-4F4E-9059-62DAB763A544}.Debug|Any CPU.Build.0 = Debug|Any CPU 110 | {EC522CB8-5E90-4F4E-9059-62DAB763A544}.Debug|x64.ActiveCfg = Debug|Any CPU 111 | {EC522CB8-5E90-4F4E-9059-62DAB763A544}.Debug|x64.Build.0 = Debug|Any CPU 112 | {EC522CB8-5E90-4F4E-9059-62DAB763A544}.Debug|x86.ActiveCfg = Debug|Any CPU 113 | {EC522CB8-5E90-4F4E-9059-62DAB763A544}.Debug|x86.Build.0 = Debug|Any CPU 114 | {EC522CB8-5E90-4F4E-9059-62DAB763A544}.Release|Any CPU.ActiveCfg = Release|Any CPU 115 | {EC522CB8-5E90-4F4E-9059-62DAB763A544}.Release|Any CPU.Build.0 = Release|Any CPU 116 | {EC522CB8-5E90-4F4E-9059-62DAB763A544}.Release|x64.ActiveCfg = Release|Any CPU 117 | {EC522CB8-5E90-4F4E-9059-62DAB763A544}.Release|x64.Build.0 = Release|Any CPU 118 | {EC522CB8-5E90-4F4E-9059-62DAB763A544}.Release|x86.ActiveCfg = Release|Any CPU 119 | {EC522CB8-5E90-4F4E-9059-62DAB763A544}.Release|x86.Build.0 = Release|Any CPU 120 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 121 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350}.Debug|Any CPU.Build.0 = Debug|Any CPU 122 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350}.Debug|x64.ActiveCfg = Debug|Any CPU 123 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350}.Debug|x64.Build.0 = Debug|Any CPU 124 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350}.Debug|x86.ActiveCfg = Debug|Any CPU 125 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350}.Debug|x86.Build.0 = Debug|Any CPU 126 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350}.Release|Any CPU.ActiveCfg = Release|Any CPU 127 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350}.Release|Any CPU.Build.0 = Release|Any CPU 128 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350}.Release|x64.ActiveCfg = Release|Any CPU 129 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350}.Release|x64.Build.0 = Release|Any CPU 130 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350}.Release|x86.ActiveCfg = Release|Any CPU 131 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350}.Release|x86.Build.0 = Release|Any CPU 132 | EndGlobalSection 133 | GlobalSection(SolutionProperties) = preSolution 134 | HideSolutionNode = FALSE 135 | EndGlobalSection 136 | GlobalSection(NestedProjects) = preSolution 137 | {14A80081-D97A-405D-9A49-C686ABF00842} = {43FBDDAF-4E0A-4D82-BCB2-002472E7F170} 138 | {BB0051D2-8692-449E-B20F-6DBD605C7553} = {3D12606D-9919-4874-A10C-E37F1CB84CBC} 139 | {1661E77C-1DD4-4920-9345-C86D04B37DD6} = {3D12606D-9919-4874-A10C-E37F1CB84CBC} 140 | {70B2741D-D9AC-47B2-9D0C-78478562C6D5} = {3D12606D-9919-4874-A10C-E37F1CB84CBC} 141 | {A9F69171-7544-4029-A2EA-4E6FF49D2E1C} = {43FBDDAF-4E0A-4D82-BCB2-002472E7F170} 142 | {0DDF9662-A5E5-41B2-ADF2-D38447312611} = {3D12606D-9919-4874-A10C-E37F1CB84CBC} 143 | {EC522CB8-5E90-4F4E-9059-62DAB763A544} = {3D12606D-9919-4874-A10C-E37F1CB84CBC} 144 | {47CDCE8F-BF6F-4FFB-8606-FC7835C68350} = {3D12606D-9919-4874-A10C-E37F1CB84CBC} 145 | EndGlobalSection 146 | GlobalSection(ExtensibilityGlobals) = postSolution 147 | SolutionGuid = {4BFB89E1-195E-46FB-8AB6-7632542E82D5} 148 | EndGlobalSection 149 | EndGlobal 150 | --------------------------------------------------------------------------------