Skip to content

Commit

Permalink
Feature/dependencies scrub (#2906)
Browse files Browse the repository at this point in the history
* fixed last remaining failing unit tests

* Split serializer into request/response AND source serializer. Scrub json.net related classes from public API

* Writing test for source serialization, making sure it respects the custom user serializer

* formatting

* validated more API's are using source serializer correctly for writing

* we now randomly seed when running on the commandline and we show replay command at the end of the run always

* test framework can now run with or without source serializer this is random on the commandline and pinnable on both commandline and vanilla test runners. unit tests succeeds for both with and without source serializer, onwards to integration tests

* Don't bleed Union<T,T> into JoinField, since its expected to be on folks documents it does not play well with SourceSerializer's, had it working in a previous commit but this limits the ammount of places that need patching.

* clearly separate Source and RequestResponse Serializers by name

* We can now offer a custom json net serializer that will fallback to our serializer for known NEST types in _source e.g JoinField, or QueryContainer when indexing percolation queries

* RevertBackToBuiltInConverter now works as expected

* fixed failing unit test due to new property on Project

* Several improvements

LazyDocument is now source serializer aware.

Explain() Search() MultiGet() SimulatePipeline() Get() now all have
integration tests for custom source deserialization

* Suggest() is now source serializer aware

* fixed all unit tests, reverted changes to source converter and documentjson converter since the proposed optimization still used the internal writer and thus the internal serializer settings

* ApiTestBase now uses source convverter by default as well, more like this query document now uses source converter

* TopHits aggregations now support custom source serializer

* Scripted metric now uses source serializer

* FieldsValues is now also source serializer aware

* update unit tests now that we have a non nullable int in Project

* update paket targets file

* ILRepack internalizes JSON.NET, Mono.Cecil rewrites its namespace

* updated Mono.Cecil to the latest beta, this one does not lock the assembly file and explicitly allows overwriting existing files

* Introduced new nuget package NEST.JsonNetSerializer which folks can reference should they need to customize the way their _source and field values are (de)serialized using Json.NET

* added new documentation around customizing serialization'

* attempt 1

* attempt 2

* All unit tests now pass when testing against local nuget package for NEST.

Simplified Serialization testbase

Return of Gimme.Lock started seeing Bogus blocking on Populate again.

* canary builds now test the nuget package at the end of its build run.

* make appveyor run the canary build and include appveyor.yml as solution item

* ILRepack and mono do not mix, prevent calling canary/release on mono systems for now

* move to async token read in json.net 10.0.1, idea being that its less bad to buffer in a JObject than to read the stream in a blocking fashion. cc @codebrain @russcam

* Add SerializeAsync to interface

* Prevent boxing on PostData<object>

`PostData<object>` was was introduced as a union'esque type that
`byte[]/string/object/IEnumerable<string>/<IEnumerable<object>` could
all implicitly converter to automagically making the lowel client easy
to use without exploding the already massive method list on
ILowLevelClient.

This abstraction meant that we always boxed for NEST types to object.

In preparation for all the serialization/perf work we want to do in 6.x
it would be great if we can take `T` that we we want to serialize all
the way to the `IElasticsearchSerializer` which we can do with this PR
now.

We sacrifice on the implicit operators in the low level client (for the
most part: byte[] and string still do). TBF most of the time I've seen
people use the low level client they would instantiate
`PostData<object>` directly already. Or worse see: #2656. We weren't
really helping anyone here.

`PostData<object>` now has a base class `PostData` that we use
throughout. There is also `SerializableData<T>` subclass that we now use
for anything that needs serializing.

This dispatcher is now aware of what generics an `IRequest`
implementation has. Because of this our hack to make `Index(document)`
and `CreateIndex(document)` work when document == IIndexRequest or not
no longer works. Rather than fighting C# resolution of overloads (and
its lack of not constraints :() the methods are now called
`IndexDocument()` and `CreateDocument()`.

* update travis to dotnet 2.0.3

* walkthrough review comments from @codebrain and @russcam, updated travis.yml to support netcore 2 building netcoreapp1.x on linux

* suspicion skipping first time experience on the dotnet cli breaks setting up nuget caches properly

* put back DOTNET_SKIP_FIRST_EXPERIENCE, as it did not solve travis build

* build.sh was alway doing an install because it was checking for  add an empty paket file in src to prevent paket from adding targets to csproj file on paket install

* make sure dotnet SDK 1.0.4 is available

* 1.0.5 not 1.0.4
  • Loading branch information
Mpdreamz committed Nov 28, 2017
1 parent 388cc4f commit cd8ea82
Show file tree
Hide file tree
Showing 264 changed files with 4,590 additions and 3,816 deletions.
28 changes: 22 additions & 6 deletions .paket/Paket.Restore.targets
Expand Up @@ -38,9 +38,24 @@
<NoWarn>$(NoWarn);NU1603</NoWarn>
</PropertyGroup>

<!-- Because ReadAllText is slow on osx/linux, try to find shasum and awk -->
<PropertyGroup>
<PaketRestoreCachedHasher Condition="'$(OS)' != 'Windows_NT' And '$(PaketRestoreCachedHasher)' == '' And Exists('/usr/bin/shasum') And Exists('/usr/bin/awk')">/usr/bin/shasum $(PaketRestoreCacheFile) | /usr/bin/awk '{ print $1 }'</PaketRestoreCachedHasher>
<PaketRestoreLockFileHasher Condition="'$(OS)' != 'Windows_NT' And '$(PaketRestoreLockFileHash)' == '' And Exists('/usr/bin/shasum') And Exists('/usr/bin/awk')">/usr/bin/shasum $(PaketLockFilePath) | /usr/bin/awk '{ print $1 }'</PaketRestoreLockFileHasher>
</PropertyGroup>

<!-- If shasum and awk exist get the hashes -->
<Exec Condition=" '$(PaketRestoreCachedHasher)' != '' " Command="$(PaketRestoreCachedHasher)" ConsoleToMSBuild='true'>
<Output TaskParameter="ConsoleOutput" PropertyName="PaketRestoreCachedHash" />
</Exec>
<Exec Condition=" '$(PaketRestoreLockFileHasher)' != '' " Command="$(PaketRestoreLockFileHasher)" ConsoleToMSBuild='true'>
<Output TaskParameter="ConsoleOutput" PropertyName="PaketRestoreLockFileHash" />
</Exec>

<PropertyGroup Condition="Exists('$(PaketRestoreCacheFile)') ">
<PaketRestoreCachedHash>$([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)'))</PaketRestoreCachedHash>
<PaketRestoreLockFileHash>$([System.IO.File]::ReadAllText('$(PaketLockFilePath)'))</PaketRestoreLockFileHash>
<!-- if no hash has been done yet fall back to just reading in the files and comparing them -->
<PaketRestoreCachedHash Condition=" '$(PaketRestoreCachedHash)' == '' ">$([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)'))</PaketRestoreCachedHash>
<PaketRestoreLockFileHash Condition=" '$(PaketRestoreLockFileHash)' == '' ">$([System.IO.File]::ReadAllText('$(PaketLockFilePath)'))</PaketRestoreLockFileHash>
<PaketRestoreRequired>true</PaketRestoreRequired>
<PaketRestoreRequired Condition=" '$(PaketRestoreLockFileHash)' == '$(PaketRestoreCachedHash)' ">false</PaketRestoreRequired>
<PaketRestoreRequired Condition=" '$(PaketRestoreLockFileHash)' == '' ">true</PaketRestoreRequired>
Expand Down Expand Up @@ -137,27 +152,28 @@

<Target Name="PaketOverrideNuspec" AfterTargets="GenerateNuspec" Condition="('$(IsPackable)' == '' Or '$(IsPackable)' == 'true') And Exists('$(MSBuildProjectDirectory)/obj/$(MSBuildProjectFile).references')" >
<ItemGroup>
<_NuspecFilesNewLocation Include="$(BaseIntermediateOutputPath)$(Configuration)/*.nuspec"/>
<_NuspecFilesNewLocation Include="$(BaseIntermediateOutputPath)$(Configuration)\*.nuspec"/>
</ItemGroup>

<PropertyGroup>
<PaketProjectFile>$(MSBuildProjectDirectory)/$(MSBuildProjectFile)</PaketProjectFile>
<ContinuePackingAfterGeneratingNuspec>true</ContinuePackingAfterGeneratingNuspec>
<UseNewPack>false</UseNewPack>
<UseNewPack Condition=" '$(NuGetToolVersion)' != '4.0.0' ">true</UseNewPack>
<AdjustedNuspecOutputPath>$(BaseIntermediateOutputPath)$(Configuration)/</AdjustedNuspecOutputPath>
<AdjustedNuspecOutputPath>$(BaseIntermediateOutputPath)$(Configuration)</AdjustedNuspecOutputPath>
<AdjustedNuspecOutputPath Condition="@(_NuspecFilesNewLocation) == ''">$(BaseIntermediateOutputPath)</AdjustedNuspecOutputPath>
</PropertyGroup>

<ItemGroup>
<_NuspecFiles Include="$(AdjustedNuspecOutputPath)*.nuspec"/>
<_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.nuspec"/>
</ItemGroup>

<Exec Command='$(PaketCommand) fix-nuspecs files "@(_NuspecFiles)" project-file "$(PaketProjectFile)" ' Condition="@(_NuspecFiles) != ''" />

<ConvertToAbsolutePath Condition="@(_NuspecFiles) != ''" Paths="@(_NuspecFiles)">
<Output TaskParameter="AbsolutePaths" PropertyName="NuspecFileAbsolutePath" />
</ConvertToAbsolutePath>
</ConvertToAbsolutePath>


<!-- Call Pack -->
<PackTask Condition="$(UseNewPack)"
Expand Down
12 changes: 10 additions & 2 deletions .travis.yml
Expand Up @@ -3,8 +3,16 @@ solution: src/Elasticsearch.sln
script: ./build.sh test
dist: trusty
mono: 4.6.2
dotnet: 1.0.1
dotnet: 2.0.3
env:
global:
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
- DOTNET_CLI_TELEMETRY_OPTOUT: 1
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
addons:
apt:
sources:
- sourceline: 'deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-trusty-prod trusty main'
key_url: 'https://packages.microsoft.com/keys/microsoft.asc'
packages:
- dotnet-sharedframework-microsoft.netcore.app-1.1.2
- dotnet-sharedframework-microsoft.netcore.app-1.0.5
2 changes: 1 addition & 1 deletion appveyor.yml
@@ -1,7 +1,7 @@
version: 1.0.{build}
image: Visual Studio 2017
build_script:
- cmd: build.bat
- cmd: build.bat canary
test: off
environment:
DOTNET_CLI_TELEMETRY_OPTOUT: true
Expand Down
4 changes: 2 additions & 2 deletions build.sh
Expand Up @@ -3,6 +3,6 @@ FAKE="packages/build/FAKE/tools/FAKE.exe"
BUILDSCRIPT="build/scripts/Targets.fsx"

mono .paket/paket.bootstrapper.exe
if [[ -f .paket.lock ]]; then mono .paket/paket.exe restore; fi
if [[ ! -f .paket.lock ]]; then mono .paket/paket.exe install; fi
if [[ -f paket.lock ]]; then mono .paket/paket.exe restore; fi
if [[ ! -f paket.lock ]]; then mono .paket/paket.exe install; fi
mono $FAKE $BUILDSCRIPT "cmdline=$*" --fsiargs -d:MONO
2 changes: 1 addition & 1 deletion build/Clients.Common.targets
Expand Up @@ -22,7 +22,7 @@
<FileVersion>$(CurrentAssemblyFileVersion)</FileVersion>

<SignAssembly Condition="'$(DotNetCoreOnly)'==''">true</SignAssembly>
<AssemblyOriginatorKeyFile Condition="'$(DotNetCoreOnly)'==''">..\..\build\keys\keypair.snk</AssemblyOriginatorKeyFile>
<AssemblyOriginatorKeyFile Condition="'$(DotNetCoreOnly)'==''">$(MSBuildThisFileDirectory)\keys\keypair.snk</AssemblyOriginatorKeyFile>
<GenerateDocumentationFile Condition="'$(DotNetCoreOnly)'==''">true</GenerateDocumentationFile>
<NoWarn>1591,1572,1571,1573,1587,1570</NoWarn>
<Prefer32Bit>false</Prefer32Bit>
Expand Down
44 changes: 44 additions & 0 deletions build/NEST.JsonNetSerializer.nuspec
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>NEST.JsonNetSerializer</id>
<version>$version$</version>
<title>Hook up your own Json.Net serializer in NEST in an isolated fashion</title>
<authors>Elastic and contributors</authors>
<owners>Elastic</owners>
<licenseUrl>https://github.com/elastic/elasticsearch-net/blob/master/license.txt</licenseUrl>
<projectUrl>https://github.com/elastic/elasticsearch-net</projectUrl>
<iconUrl>https://raw.githubusercontent.com/elastic/elasticsearch-net/master/build/nuget-icon.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>NEST 6.x ships with an internalized Json.NET, this package opens the (de)serialization up for your own custom Json.NET serializer</description>
<summary>NEST 6.x ships with an internalized Json.NET, this package opens the (de)serialization up for your own custom Json.NET serializer</summary>
<releaseNotes>See https://github.com/elastic/elasticsearch-net/releases/tag/$version$</releaseNotes>
<copyright>2017-$year$ Elasticsearch BV</copyright>
<tags>elasticsearch,elastic,search,lucene,nest,serializer,json</tags>
<dependencies>
<group targetFramework=".NETFramework4.5">
<dependency id="NEST" version="[$version$, $nextMajorVersion$)" />
<dependency id="Newtonsoft.Json" version="[$jsonDotNetCurrentVersion$, $jsonDotNetNextVersion$)" />
</group>
<group targetFramework=".NETFramework4.6">
<dependency id="NEST" version="[$version$, $nextMajorVersion$)" />
<dependency id="Newtonsoft.Json" version="[$jsonDotNetCurrentVersion$, $jsonDotNetNextVersion$)" />
</group>
<group targetFramework=".NETStandard1.3">
<dependency id="NETStandard.Library" version="[1.6.0, )" />
<dependency id="NEST" version="[$version$, $nextMajorVersion$)" />
<dependency id="Newtonsoft.Json" version="[$jsonDotNetCurrentVersion$, $jsonDotNetNextVersion$)" />
</group>
</dependencies>
</metadata>
<files>
<file src="output\Nest.JsonNetSerializer\net45\Nest.JsonNetSerializer.dll" target="lib\net45"/>
<file src="output\Nest.JsonNetSerializer\net45\Nest.JsonNetSerializer.XML" target="lib\net45"/>

<file src="output\Nest.JsonNetSerializer\net46\Nest.JsonNetSerializer.dll" target="lib\net46"/>
<file src="output\Nest.JsonNetSerializer\net46\Nest.JsonNetSerializer.XML" target="lib\net46"/>

<file src="output\Nest.JsonNetSerializer\netstandard1.3\Nest.JsonNetSerializer.dll" target="lib\netstandard1.3"/>
<file src="output\Nest.JsonNetSerializer\netstandard1.3\Nest.JsonNetSerializer.xml" target="lib\netstandard1.3"/>
</files>
</package>
11 changes: 7 additions & 4 deletions build/NEST.nuspec
Expand Up @@ -18,19 +18,22 @@
<dependencies>
<group targetFramework=".NETFramework4.5">
<dependency id="Elasticsearch.Net" version="[$version$, $nextMajorVersion$)" />
<dependency id="Newtonsoft.Json" version="[$jsonDotNetCurrentVersion$, $jsonDotNetNextVersion$)" />
</group>
<group targetFramework=".NETFramework4.6">
<dependency id="Elasticsearch.Net" version="[$version$, $nextMajorVersion$)" />
<dependency id="Newtonsoft.Json" version="[$jsonDotNetCurrentVersion$, $jsonDotNetNextVersion$)" />
</group>
<group targetFramework=".NETStandard1.3">
<dependency id="Elasticsearch.Net" version="[$version$, $nextMajorVersion$)" />
<dependency id="NETStandard.Library" version="[1.6.0, )" />
<dependency id="System.Collections.Specialized" version="[4.3.0, )" />
<dependency id="System.Reflection.TypeExtensions" version="[4.3.0, )" />
<dependency id="System.Linq.Queryable" version="[4.0.1, )" />
<dependency id="Elasticsearch.Net" version="[$version$, $nextMajorVersion$)" />
<dependency id="Newtonsoft.Json" version="[$jsonDotNetCurrentVersion$, $jsonDotNetNextVersion$)" />

<!-- since we no longer reference JSON.NET (internalized) we do need to ship with its System references -->
<dependency id="System.ComponentModel.TypeConverter" version="[4.3.0, )"/>
<dependency id="System.Runtime.Serialization.Formatters" version="[4.3.0, )"/>
<dependency id="System.Runtime.Serialization.Primitives" version="[4.3.0, )"/>
<dependency id="System.Xml.XmlDocument" version="[4.3.0, )"/>
</group>
</dependencies>
</metadata>
Expand Down
82 changes: 79 additions & 3 deletions build/scripts/Building.fsx
@@ -1,6 +1,8 @@
#I @"../../packages/build/FAKE/tools"
#I @"../../packages/build/FSharp.Data/lib/net40"
#I @"../../packages/build/Mono.Cecil/lib/net40"
#r @"FakeLib.dll"
#r @"Mono.Cecil.dll"
#r @"FSharp.Data.dll"
#nowarn "0044" //TODO sort out FAKE 5

Expand All @@ -10,8 +12,10 @@

open System
open System.IO
open System.Reflection
open Fake
open FSharp.Data
open Mono.Cecil

open Paths
open Projects
Expand All @@ -21,15 +25,15 @@ open Versioning
module Build =

let private runningRelease = hasBuildParam "version" || hasBuildParam "apikey" || getBuildParam "target" = "canary" || getBuildParam "target" = "release"
let private quickBuild = not (getBuildParam "target" = "release" || getBuildParam "target" = "canary")

type private GlobalJson = JsonProvider<"../../global.json">
let private pinnedSdkVersion = GlobalJson.GetSample().Sdk.Version
if isMono then setProcessEnvironVar "TRAVIS" "true"

let private buildingOnTravis = getEnvironmentVarAsBool "TRAVIS"

let private sln = sprintf "src/Elasticsearch%s.sln" (if buildingOnTravis then ".DotNetCoreOnly" else "")

let private compileCore incremental =
if not (DotNetCli.isInstalled()) then failwith "You need to install the dotnet command line SDK to build for .NET Core"
let runningSdkVersion = DotNetCli.getVersion()
Expand Down Expand Up @@ -76,4 +80,76 @@ module Build =
CleanDir Paths.BuildOutput
DotNetCli.RunCommand (fun p -> { p with TimeOut = TimeSpan.FromMinutes(3.) }) "clean src/Elasticsearch.sln -c Release" |> ignore
DotNetProject.All |> Seq.iter(fun p -> CleanDir(Paths.BinFolder p.Name))


type CustomResolver(folder) =
inherit DefaultAssemblyResolver()
member this.Folder = folder;

override this.Resolve name =
try
base.Resolve name
with
| ex ->
AssemblyDefinition.ReadAssembly(Path.Combine(folder, "Elasticsearch.Net.dll"));

let RewriteNamespace nest f =
let folder = Paths.ProjectOutputFolder nest f
let nestDll = sprintf "%s/%s.dll" folder nest.Name
let nestRewrittenDll = sprintf "%s/%s-rewriten.dll" folder nest.Name
use resolver = new CustomResolver(folder)
let readerParams = ReaderParameters( AssemblyResolver = resolver, ReadWrite = true );
use nestAssembly = AssemblyDefinition.ReadAssembly(nestDll, readerParams);

for item in nestAssembly.MainModule.Types do
item.Namespace <-
match item.Namespace.StartsWith("Newtonsoft.Json") with
| false -> item.Namespace
| true -> item.Namespace.Replace("Newtonsoft.Json", "Nest.Json")

//touch custom attribute arguments (not updated automatically)
let touchAttributes (attributes :Mono.Collections.Generic.Collection<CustomAttribute>) =
for attr in attributes do
if attr.HasConstructorArguments then
for constArg in attr.ConstructorArguments do
let isType = constArg.Type.Name = "Type"
ignore()

for t in nestAssembly.MainModule.Types do
touchAttributes t.CustomAttributes
for prop in t.Properties do
touchAttributes prop.CustomAttributes

let key = File.ReadAllBytes(Paths.Keys("keypair.snk"))
let kp = StrongNameKeyPair(key)
let wp = WriterParameters ( StrongNameKeyPair = kp);
nestAssembly.Write(wp) |> ignore;

let private ilRepackInternal() =
let fw = if isMono then [DotNetFramework.NetStandard1_3] else DotNetFramework.All
for f in fw do
let nest = Project Project.Nest
let folder = Paths.ProjectOutputFolder nest f
let nestDll = sprintf "%s/%s.dll" folder nest.Name
let nestMergedDll = sprintf "%s/%s-merged.dll" folder nest.Name
let jsonDll = sprintf "%s/Newtonsoft.Json.dll" folder
let keyFile = Paths.Keys("keypair.snk");
let options =
[
"/keyfile:", keyFile;
"/internalize", "";
"/lib:", folder;
"/out:", nestDll;
]
|> List.map (fun (p,v) -> sprintf "%s%s" p v)

let args = [nestDll; jsonDll;] |> List.append options;

Tooling.ILRepack.Exec args |> ignore
RewriteNamespace nest f |> ignore

let ILRepack() =
//ilrepack on mono crashes pretty hard on my machine
match isMono with
| true -> ignore()
| false -> ilRepackInternal()

31 changes: 28 additions & 3 deletions build/scripts/Commandline.fsx
Expand Up @@ -30,14 +30,31 @@ Targets:
also pushes to upstream (myget)
NOTE: both the `test` and `integrate` targets can be suffixed with `-all` to force the tests against all suported TFM's
Execution hints can be provided anywhere on the command line
- skiptests : skip running tests as part of the target chain
- source_serialization : force tests to use a client with custom source serialization
- seed:<N> : provide a seed to run the tests with.
"""

module Commandline =
type MultiTarget = All | One

let private args = getBuildParamOrDefault "cmdline" "build" |> split ' '

let skipTests = args |> List.exists (fun x -> x = "skiptests")
let private filteredArgs = args |> List.filter (fun x -> x <> "skiptests")
let forceSourceSerialization = args |> List.exists (fun x -> x = "source_serialization")
let seed =
match args |> List.tryFind (fun x -> x.StartsWith("seed:")) with
| Some t -> t.Replace("seed:", "")
| _ -> ""

let private filteredArgs =
args
|> List.filter (
fun x ->
x <> "skiptests" && x <> "source_serialization" && not (x.StartsWith("seed:"))
)

let multiTarget =
match (filteredArgs |> List.tryHead) with
Expand All @@ -49,6 +66,12 @@ module Commandline =
| Some t -> t.Replace("-all", "")
| _ -> "build"

let validMonoTarget =
match target with
| "release"
| "canary" -> false
| _ -> true

let needsFullBuild =
match (target, skipTests) with
| (_, true) -> true
Expand All @@ -59,10 +82,11 @@ module Commandline =

let needsClean =
match (target, skipTests) with
| (_, true) -> true
| ("release", _) -> true
//dotnet-xunit needs to a build of its own anyways
| ("test", _)
| ("integrate", _) -> false
| ("integrate", _)
| ("build", _) -> false
| _ -> true

let arguments =
Expand Down Expand Up @@ -134,6 +158,7 @@ module Commandline =
setBuildParam "clusterfilter" "ConnectionReuse"
setBuildParam "numberOfConnections" numberOfConnections

| ["temp"; ] -> ignore()
| ["canary"; ] -> ignore()
| ["canary"; apiKey ] ->
setBuildParam "apiKey" apiKey
Expand Down
5 changes: 4 additions & 1 deletion build/scripts/Paths.fsx
Expand Up @@ -37,7 +37,10 @@ module Paths =

let ProjFile(project:DotNetProject) =
match project with
| Project p -> sprintf "%s/%s/%s.csproj" SourceFolder project.Name project.Name
| Project p ->
match p with
| NestJsonNetSerializer -> sprintf "%s/Serializers/%s/%s.csproj" SourceFolder project.Name project.Name
| _ -> sprintf "%s/%s/%s.csproj" SourceFolder project.Name project.Name
| PrivateProject p ->
match p with
| Tests -> sprintf "%s/%s/%s.csproj" SourceFolder project.Name project.Name
Expand Down

0 comments on commit cd8ea82

Please sign in to comment.