Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Command-line interface (CLI) #35

Closed
felix-b opened this issue Mar 28, 2017 · 17 comments
Closed

Command-line interface (CLI) #35

felix-b opened this issue Mar 28, 2017 · 17 comments

Comments

@felix-b
Copy link
Member

felix-b commented Mar 28, 2017

Create command line interface that will allow invocation of a pluggable set of commands.

The implementation logic of commands can be either coupled or decoupled from the CLI (this is a question; as for Unix philosophy, the CLI is the master).

Currently, we need two commands:

nwheels service --build path/to/microservice.xml
nwheels service --run path/to/microservice.xml path/to/environment.xml

or combination of both:

nwheels service --build --run path/to/microservice.xml path/to/environment.xml
@felix-b felix-b added this to the 01 First happy path milestone Mar 28, 2017
@felix-b felix-b self-assigned this Mar 28, 2017
@felix-b
Copy link
Member Author

felix-b commented Mar 28, 2017

First step is to choose command-line library. My suggestion is System.CommandLine from corefxlab.

Any feedback is welcome...

@ILyaIL83
Copy link
Collaborator

Is there a sense to send full files paths as arguments? Nowadays their short names are hard coded and documented. So why not to send directory path?

@felix-b
Copy link
Member Author

felix-b commented Mar 29, 2017

Basically I agree. It makes sense to expect microservice.xml to be in the source directory.

But what if environment.xml comes from somewhere else? Current structure of environment.xml allows only specify one environment. Which means that environment.xml in the sources will specify environment for local dev boxes. Therefore, during deployment procedure to test/prod environments, the contents of environment.xml must come from somewhere else.

UPDATE

I've got half way through implementation, and I realized that I want to change the syntax.

Before: one service command took both --build and --run responsibilities.
After: there are two separate commands, publish and run. Note that I renamed build to publish.

Before: build+run combination for F5 debugging was specified as service --build --run
After: publish+run combination for F5 debugging is performed by run command

nwheels publish

Purpose

Builds microservice working folder, which then includes all necessary files to run a microservice.

Syntax

nwheels publish [/path/to/source/folder] [--out /path/to/publish/folder] [--env /path/to/environment.xml]

Parameters:

  • /path/to/source/folder - optional. Specifies source folder of microservice project, where both microservice.xml and .csproj files reside. The path can be relative to current directory. If not specified, the default is current directory.

Options:

  • --out /path/to/publish/folder - optional. Destination microservice working folder. The folder will be created if it doesn't exist. If the folder exists, existing files will be overwritten. The path can be relative to source folder. If not specified, the default is ./bin/microservice.working relative to source folder.

  • --env /path/to/environment.xml - optional. Path to environment XML file to copy to microservice working folder. The file can have any arbitrary name (e.g. env-qa1.xml). The path can be relative to source folder. If not specified, the default is environment.xml in the source folder.

nwheels run

Purpose

Runs microservice from working folder that was built with nwheels publish. Optionally, performs publish from source folder as a preliminary step.

Syntax

nwheels run [/path/to/publish-or-source/folder]

Parameters:

  • /path/to/publish-or-source/folder - optional. Specifies microservice folder. If not specified, default is current directory. Depending on whether microservice folder is source or publish, one of the following happens:
    • If microservice folder is publish, the command runs a microservice instance.
    • If microservice folder is source, the command first internally performs nwheels publish to ./bin/microservice.working under the source folder, using environment.xml in the source folder. Then it runs microservice instance.

@felix-b
Copy link
Member Author

felix-b commented Mar 30, 2017

One doubt: I'm sure want to test the CLI, but should I do unit testing, or system testing?

Meanwhile I prepared some infra for unit testing the CLI, but I'm not sure it is the best approach...

@felix-b
Copy link
Member Author

felix-b commented Mar 31, 2017

I came to conclusion that unit testing the CLI doesn't worth effort and complexity it adds. In the last commit to #37, I got rid of unit test project and ICommandContext abstraction. The code got simple again. We will have to system-test it in the future.

@felix-b
Copy link
Member Author

felix-b commented Mar 31, 2017

A problem. While the CLI is going to be an awesome tool for deployment... it doesn't resolve our current issue #32 for F5 debugging in Visual Studio:

In my implementation of nwheels publish, I use dotnet publish on projects that correspond to modules in microservice.xml. This is to avoid implementing a kind of dotnet-publish by myself.

Now, dotnet-publish has two problems with the F5 scenario:

  • it is slow; it takes about 20 seconds to publish a microservice of 4 modules; this is quite annoying if I do F5 frequently.
  • it encounters "cannot access file... in use by another process..." errors when the solution is open in Visual Studio; so it cannot be used for F5 debugging.

It looks like the best approach for F5 debugging is load modules from where they were compiled. I know we had problems there, so we opted to bring all binaries together by copying. Maybe we should attack that issue again -- maybe like this:

AssemblyLoadContext.Default.Resolving += this.OnResolve;

I found it here:
https://github.com/Microsoft/vstest/blob/c701070abb126853b58800cedd7554482471ac7d/src/Microsoft.TestPlatform.Common/Utilities/AssemblyResolver.cs

@ILyaIL83
Copy link
Collaborator

So, how many modes/scenarios/ways do we have/plan for now(release/debug directories and packages)?

@ILyaIL83
Copy link
Collaborator

ILyaIL83 commented Apr 1, 2017

Why not to add to "run" command new argument which will tell what to do before - publish or copy. This way we can use copy for developing and publish for integration tests and deployment?

@felix-b
Copy link
Member Author

felix-b commented Apr 1, 2017

My current vision is as follows:

  • For F5 debugging in Visual Studio, I'm currently investigating handling the AssemblyLoadContext.Resolving event. Meanwhile is looks promising. In this way, we won't have to copy anything when working in Visual Studio.
  • For microservice deployment and system tests, we will use the CLI nwheels publish and nwheels run. Note that the CLI itself will also be published to the microservice folder. When we want to run a micriservice, we will execute nwheels run from the microservice folder (though nwheels run is also able to run from a different location).
  • Currently, nwheels run can automatically perform nwheels publish (I explained it in the nwheels run description above). But if we handle F5 debugging without the CLI -- then it is no longer necessary.
  • Doing just copy to build "runnable" microservice folder is not enough. The DLLs from NuGet packages and many .NET Framework assemblies don't present in the Bin folder. In order to correctly build a "runnable" microservice folder, we need to use dotnet publish. (Or we can develop our own dotnet-publish, but I don't believe we want to).

@ILyaIL83
Copy link
Collaborator

ILyaIL83 commented Apr 1, 2017

OK. So we are good again.

@felix-b
Copy link
Member Author

felix-b commented Apr 1, 2017

AssemblyLoadContext.Resolving does the trick. I am able to load an assembly from any location. The idea is to have a list of folders, and every time an assembly needs to be loaded, search in these folders.

In deployment, there is only one folder to search -- the publish folder.

For F5 debugging, I assume certain structure of solution folders. Currently, I assume that all projects reside in folders named after their assemblies, in immediate subfolders of the solution folder. This has to be extensible, so that a concrete application will be able to plug in its own structure.

UPDATE: I think we can inspect solution structure automatically through SLN and CSPROJ (reusing Microsoft APIs for that, of course).

@felix-b
Copy link
Member Author

felix-b commented Apr 1, 2017

The last problem with F5 debugging is locating assemblies from NuGet packages. I can just go to %UserProfile%\.NuGet\Packages\package_name\package_version\lib\package_platform.
But it's not that simple. In the Resolving handler, I have details of assembly, not its package:

  • assembly name doesn't always equal to package name
  • assembly version doesn't always equal to package version; sometimes version redirects need to be taken into account.
  • package platform is not always "netstandard1.6"; there needs to be additional logic of platform compatibility.

All these lead me to conclude that I don't want to write that logic. It is already written. I only have to figure out how I can reuse it. Currently looking through https://github.com/NuGet/NuGet.Client, https://github.com/dotnet/cli, and https://github.com/Microsoft/msbuild.

@felix-b
Copy link
Member Author

felix-b commented Apr 1, 2017

We can also ask our users to do publish at least once, but that's ugly; every time they'll add another package reference, they'll need to do publish again; and then we'll answer their issues, telling them they forgot to do so...

@felix-b
Copy link
Member Author

felix-b commented Apr 2, 2017

Progress in locating assemblies from NuGet packages. MSBuild has a target for that, named ComputeFilesToPublish. It seems to do the work.

This target in turn uses a task class named ResolvePublishAssemblies (here it is: https://github.com/dotnet/sdk/blob/6e8935b1a75240d14948a23c0423198e4bba76a4/src/Tasks/Microsoft.NET.Build.Tasks/ResolvePublishAssemblies.cs).

If I use necessary MSBuild packages from NuGet, I can run that task in-process from the CLI. Like this: http://codefocus.blogspot.co.il/2008/10/running-msbuild-tasks-programatically.html

@felix-b
Copy link
Member Author

felix-b commented Apr 3, 2017

Good news: the ResolvePublishAssemblies task indeed does what we need.

Meanwhile I figured out how to run it with MSBuild. With the following project file p1.proj:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    
    <UsingTask 
        TaskName="ResolvePublishAssemblies" 
        AssemblyFile="C:\Program Files\dotnet\sdk\1.0.0\Sdks\Microsoft.NET.Sdk\build\..\tools\netcoreapp1.0/Microsoft.NET.Build.Tasks.dll" />

    <Target Name="Build">  
        <ResolvePublishAssemblies 
            ProjectPath="C:\Home\NWheels\Source\NWheels.Injection.Adapters.Autofac\NWheels.Injection.Adapters.Autofac.csproj"
            AssetsFilePath="C:\Home\NWheels\Source\NWheels.Injection.Adapters.Autofac\obj\project.assets.json"
            TargetFramework=".NETStandard,Version=v1.6">
            <Output TaskParameter="AssembliesToPublish" ItemName="ResolvedAssembliesToPublish" />
        </ResolvePublishAssemblies>
        <Message Text="%(DestinationSubPath)=@(ResolvedAssembliesToPublish)" Importance="high" />  
    </Target>  
    
</Project>

the command:

dotnet msbuild p1.proj /nologo

produces the following output:

  Autofac.dll=C:\Users\felix.b\.nuget\packages\autofac\4.4.0\lib\netstandard1.1\Autofac.dll
  runtimes\debian.8-x64\native\System.Security.Cryptography.Native.OpenSsl.so=C:\Users\felix.b\.nuget\packages\runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl\4.3.0\runtimes\debian.8-x64\native\System.Security.Cryptography.Native.OpenSsl.so
  runtimes\fedora.23-x64\native\System.Security.Cryptography.Native.OpenSsl.so=C:\Users\felix.b\.nuget\packages\runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl\4.3.0\runtimes\fedora.23-x64\native\System.Security.Cryptography.Native.OpenSsl.so
  runtimes\fedora.24-x64\native\System.Security.Cryptography.Native.OpenSsl.so=C:\Users\felix.b\.nuget\packages\runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl\4.3.0\runtimes\fedora.24-x64\native\System.Security.Cryptography.Native.OpenSsl.so
  runtimes\opensuse.13.2-x64\native\System.Security.Cryptography.Native.OpenSsl.so=C:\Users\felix.b\.nuget\packages\runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl\4.3.0\runtimes\opensuse.13.2-x64\native\System.Security.Cryptography.Native.OpenSsl.so
  runtimes\opensuse.42.1-x64\native\System.Security.Cryptography.Native.OpenSsl.so=C:\Users\felix.b\.nuget\packages\runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl\4.3.0\runtimes\opensuse.42.1-x64\native\System.Security.Cryptography.Native.OpenSsl.so
  runtimes\osx.10.10-x64\native\System.Security.Cryptography.Native.Apple.dylib=C:\Users\felix.b\.nuget\packages\runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple\4.3.0\runtimes\osx.10.10-x64\native\System.Security.Cryptography.Native.Apple.dylib
  runtimes\osx.10.10-x64\native\System.Security.Cryptography.Native.OpenSsl.dylib=C:\Users\felix.b\.nuget\packages\runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl\4.3.0\runtimes\osx.10.10-x64\native\System.Security.Cryptography.Native.OpenSsl.dylib
  runtimes\rhel.7-x64\native\System.Security.Cryptography.Native.OpenSsl.so=C:\Users\felix.b\.nuget\packages\runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl\4.3.0\runtimes\rhel.7-x64\native\System.Security.Cryptography.Native.OpenSsl.so
  runtimes\ubuntu.14.04-x64\native\System.Security.Cryptography.Native.OpenSsl.so=C:\Users\felix.b\.nuget\packages\runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl\4.3.0\runtimes\ubuntu.14.04-x64\native\System.Security.Cryptography.Native.OpenSsl.so
  runtimes\ubuntu.16.04-x64\native\System.Security.Cryptography.Native.OpenSsl.so=C:\Users\felix.b\.nuget\packages\runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl\4.3.0\runtimes\ubuntu.16.04-x64\native\System.Security.Cryptography.Native.OpenSsl.so
  runtimes\ubuntu.16.10-x64\native\System.Security.Cryptography.Native.OpenSsl.so=C:\Users\felix.b\.nuget\packages\runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl\4.3.0\runtimes\ubuntu.16.10-x64\native\System.Security.Cryptography.Native.OpenSsl.so
  System.AppContext.dll=C:\Users\felix.b\.nuget\packages\system.appcontext\4.3.0\lib\netstandard1.6\System.AppContext.dll
  System.Buffers.dll=C:\Users\felix.b\.nuget\packages\system.buffers\4.3.0\lib\netstandard1.1\System.Buffers.dll
  System.Collections.Concurrent.dll=C:\Users\felix.b\.nuget\packages\system.collections.concurrent\4.3.0\lib\netstandard1.3\System.Collections.Concurrent.dll
  System.Collections.Immutable.dll=C:\Users\felix.b\.nuget\packages\system.collections.immutable\1.3.1\lib\netstandard1.0\System.Collections.Immutable.dll
  System.ComponentModel.dll=C:\Users\felix.b\.nuget\packages\system.componentmodel\4.0.1\lib\netstandard1.3\System.ComponentModel.dll
  System.Diagnostics.DiagnosticSource.dll=C:\Users\felix.b\.nuget\packages\system.diagnostics.diagnosticsource\4.3.0\lib\netstandard1.3\System.Diagnostics.DiagnosticSource.dll
  runtimes\unix\lib\netstandard1.3\System.Globalization.Extensions.dll=C:\Users\felix.b\.nuget\packages\system.globalization.extensions\4.3.0\runtimes\unix\lib\netstandard1.3\System.Globalization.Extensions.dll
  runtimes\win\lib\netstandard1.3\System.Globalization.Extensions.dll=C:\Users\felix.b\.nuget\packages\system.globalization.extensions\4.3.0\runtimes\win\lib\netstandard1.3\System.Globalization.Extensions.dll
  runtimes\unix\lib\netstandard1.3\System.IO.Compression.dll=C:\Users\felix.b\.nuget\packages\system.io.compression\4.3.0\runtimes\unix\lib\netstandard1.3\System.IO.Compression.dll
  runtimes\win\lib\netstandard1.3\System.IO.Compression.dll=C:\Users\felix.b\.nuget\packages\system.io.compression\4.3.0\runtimes\win\lib\netstandard1.3\System.IO.Compression.dll
  System.IO.Compression.ZipFile.dll=C:\Users\felix.b\.nuget\packages\system.io.compression.zipfile\4.3.0\lib\netstandard1.3\System.IO.Compression.ZipFile.dll
  System.IO.FileSystem.Primitives.dll=C:\Users\felix.b\.nuget\packages\system.io.filesystem.primitives\4.3.0\lib\netstandard1.3\System.IO.FileSystem.Primitives.dll
  System.Linq.dll=C:\Users\felix.b\.nuget\packages\system.linq\4.3.0\lib\netstandard1.6\System.Linq.dll
  System.Linq.Expressions.dll=C:\Users\felix.b\.nuget\packages\system.linq.expressions\4.3.0\lib\netstandard1.6\System.Linq.Expressions.dll
  runtimes\unix\lib\netstandard1.6\System.Net.Http.dll=C:\Users\felix.b\.nuget\packages\system.net.http\4.3.0\runtimes\unix\lib\netstandard1.6\System.Net.Http.dll
  runtimes\win\lib\netstandard1.3\System.Net.Http.dll=C:\Users\felix.b\.nuget\packages\system.net.http\4.3.0\runtimes\win\lib\netstandard1.3\System.Net.Http.dll
  System.ObjectModel.dll=C:\Users\felix.b\.nuget\packages\system.objectmodel\4.3.0\lib\netstandard1.3\System.ObjectModel.dll
  System.Reflection.Emit.dll=C:\Users\felix.b\.nuget\packages\system.reflection.emit\4.3.0\lib\netstandard1.3\System.Reflection.Emit.dll
  System.Reflection.Emit.ILGeneration.dll=C:\Users\felix.b\.nuget\packages\system.reflection.emit.ilgeneration\4.3.0\lib\netstandard1.3\System.Reflection.Emit.ILGeneration.dll
  System.Reflection.Emit.Lightweight.dll=C:\Users\felix.b\.nuget\packages\system.reflection.emit.lightweight\4.3.0\lib\netstandard1.3\System.Reflection.Emit.Lightweight.dll
  System.Reflection.TypeExtensions.dll=C:\Users\felix.b\.nuget\packages\system.reflection.typeextensions\4.3.0\lib\netstandard1.5\System.Reflection.TypeExtensions.dll
  System.Runtime.InteropServices.RuntimeInformation.dll=C:\Users\felix.b\.nuget\packages\system.runtime.interopservices.runtimeinformation\4.3.0\lib\netstandard1.1\System.Runtime.InteropServices.RuntimeInformation.dll
  runtimes\unix\lib\netstandard1.1\System.Runtime.InteropServices.RuntimeInformation.dll=C:\Users\felix.b\.nuget\packages\system.runtime.interopservices.runtimeinformation\4.3.0\runtimes\unix\lib\netstandard1.1\System.Runtime.InteropServices.RuntimeInformation.dll
  runtimes\win\lib\netstandard1.1\System.Runtime.InteropServices.RuntimeInformation.dll=C:\Users\felix.b\.nuget\packages\system.runtime.interopservices.runtimeinformation\4.3.0\runtimes\win\lib\netstandard1.1\System.Runtime.InteropServices.RuntimeInformation.dll
  System.Runtime.Loader.dll=C:\Users\felix.b\.nuget\packages\system.runtime.loader\4.3.0\lib\netstandard1.5\System.Runtime.Loader.dll
  System.Runtime.Numerics.dll=C:\Users\felix.b\.nuget\packages\system.runtime.numerics\4.3.0\lib\netstandard1.3\System.Runtime.Numerics.dll
  runtimes\osx\lib\netstandard1.6\System.Security.Cryptography.Algorithms.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.algorithms\4.3.0\runtimes\osx\lib\netstandard1.6\System.Security.Cryptography.Algorithms.dll
  runtimes\unix\lib\netstandard1.6\System.Security.Cryptography.Algorithms.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.algorithms\4.3.0\runtimes\unix\lib\netstandard1.6\System.Security.Cryptography.Algorithms.dll
  runtimes\win\lib\netstandard1.6\System.Security.Cryptography.Algorithms.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.algorithms\4.3.0\runtimes\win\lib\netstandard1.6\System.Security.Cryptography.Algorithms.dll
  runtimes\unix\lib\netstandard1.6\System.Security.Cryptography.Cng.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.cng\4.3.0\runtimes\unix\lib\netstandard1.6\System.Security.Cryptography.Cng.dll
  runtimes\win\lib\netstandard1.6\System.Security.Cryptography.Cng.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.cng\4.3.0\runtimes\win\lib\netstandard1.6\System.Security.Cryptography.Cng.dll
  runtimes\unix\lib\netstandard1.3\System.Security.Cryptography.Csp.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.csp\4.3.0\runtimes\unix\lib\netstandard1.3\System.Security.Cryptography.Csp.dll
  runtimes\win\lib\netstandard1.3\System.Security.Cryptography.Csp.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.csp\4.3.0\runtimes\win\lib\netstandard1.3\System.Security.Cryptography.Csp.dll
  runtimes\unix\lib\netstandard1.3\System.Security.Cryptography.Encoding.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.encoding\4.3.0\runtimes\unix\lib\netstandard1.3\System.Security.Cryptography.Encoding.dll
  runtimes\win\lib\netstandard1.3\System.Security.Cryptography.Encoding.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.encoding\4.3.0\runtimes\win\lib\netstandard1.3\System.Security.Cryptography.Encoding.dll
  System.Security.Cryptography.OpenSsl.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.openssl\4.3.0\lib\netstandard1.6\System.Security.Cryptography.OpenSsl.dll
  runtimes\unix\lib\netstandard1.6\System.Security.Cryptography.OpenSsl.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.openssl\4.3.0\runtimes\unix\lib\netstandard1.6\System.Security.Cryptography.OpenSsl.dll
  System.Security.Cryptography.Primitives.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.primitives\4.3.0\lib\netstandard1.3\System.Security.Cryptography.Primitives.dll
  runtimes\unix\lib\netstandard1.6\System.Security.Cryptography.X509Certificates.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.x509certificates\4.3.0\runtimes\unix\lib\netstandard1.6\System.Security.Cryptography.X509Certificates.dll
  runtimes\win\lib\netstandard1.6\System.Security.Cryptography.X509Certificates.dll=C:\Users\felix.b\.nuget\packages\system.security.cryptography.x509certificates\4.3.0\runtimes\win\lib\netstandard1.6\System.Security.Cryptography.X509Certificates.dll
  System.Text.RegularExpressions.dll=C:\Users\felix.b\.nuget\packages\system.text.regularexpressions\4.3.0\lib\netstandard1.6\System.Text.RegularExpressions.dll
  System.Threading.dll=C:\Users\felix.b\.nuget\packages\system.threading\4.3.0\lib\netstandard1.3\System.Threading.dll
  System.Threading.Tasks.Extensions.dll=C:\Users\felix.b\.nuget\packages\system.threading.tasks.extensions\4.3.0\lib\netstandard1.0\System.Threading.Tasks.Extensions.dll
  System.ValueTuple.dll=C:\Users\felix.b\.nuget\packages\system.valuetuple\4.3.0\lib\netstandard1.0\System.ValueTuple.dll
  System.Xml.ReaderWriter.dll=C:\Users\felix.b\.nuget\packages\system.xml.readerwriter\4.3.0\lib\netstandard1.3\System.Xml.ReaderWriter.dll
  System.Xml.XDocument.dll=C:\Users\felix.b\.nuget\packages\system.xml.xdocument\4.3.0\lib\netstandard1.3\System.Xml.XDocument.dll
  System.Xml.XmlDocument.dll=C:\Users\felix.b\.nuget\packages\system.xml.xmldocument\4.3.0\lib\netstandard1.3\System.Xml.XmlDocument.dll
  System.Xml.XmlSerializer.dll=C:\Users\felix.b\.nuget\packages\system.xml.xmlserializer\4.3.0\lib\netstandard1.3\System.Xml.XmlSerializer.dll

From here I easily build a dictionary of assembly file path by assembly name. In the AssemblyLoadContext.Resolving handler, given an assembly name, I just lookup its path and load it from there.

@felix-b
Copy link
Member Author

felix-b commented Apr 3, 2017

Not the way I wanted

I've abandoned attempts to re-host ResolvePublishAssemblies in-process inside the CLI, as there is no clear line in available NuGet packages.

Another intention of mine was to use Roslyn's MSBuildWorkspace to inspect SLN files and correctly determine location of module projects. I've abandoned that too, because it isn't yet available in NETStandard (dotnet/roslyn#17974).

Cutting edge is bleeding edge....

For now

To determine location of module projects, I assume simple flat solution structure:

  • module projects use default assembly names (equal to .CSPROJ file name)
  • all project folders are named after project files (but without the .CSPROJ extension)
  • all project folders reside in the solution folder

To locate all dependency assemblies, I will generate .PROJ file and run MSBuild command as shown in provious comment. I will intercept standard output and parse assembly name/path pairs into a dictionary.

@felix-b
Copy link
Member Author

felix-b commented Apr 5, 2017

Factual implementation summary

This comment summarizes design and implementation details as of the end of work.

Command: nwheels publish

Purpose

Builds microservice working folder, which then includes all necessary files to run a microservice.

Syntax

nwheels publish [/path/to/source/folder] [--out /path/to/publish/folder] [--env /path/to/environment.xml] [--no-cli] [--project-config name]

Parameters:

  • /path/to/source/folder - optional. Specifies source folder of microservice project, where both microservice.xml and .csproj files reside. The path can be relative to current directory. If not specified, the default is current directory.

Options:

  • --out /path/to/publish/folder - optional. Destination microservice working folder. The folder will be created if it doesn't exist. If the folder exists, existing files will be overwritten. The path can be relative to source folder. If not specified, the default is ./bin/microservice.working relative to source folder.

  • --env /path/to/environment.xml - optional. Path to environment XML file to copy to microservice working folder. The file can have any arbitrary name (e.g. env-qa1.xml). The path can be relative to source folder. If not specified, the default is environment.xml in the source folder.

  • --no-cli - when specified, the NWheels CLI assembly (nwheels.dll) will not be copied to the publish folder. This is useful for F5 debugging in Visual Studio, as an attempt to publish the CLI project at that moment will fail with "access denied" error. If not specified, the default is to publish the CLI.

  • --project-config name - optional. Specifies project configuration name (e.g. Debug or Release) for dotnet publish, when it compiles the projects. If not specified, the default is Release.

Command: nwheels run

Purpose

Runs microservice from publish or source folder. Optionally, performs publish from source folder as a preliminary step.

Syntax

nwheels run [/path/to/microservice/folder] [--no-publish] [--project-config name]

Parameters:

  • /path/to/microservice/folder - optional. Specifies microservice folder. If not specified, the default is current directory. Depending on whether microservice folder is source or publish, one of the following happens:
    • If microservice folder is publish folder created earlier with nwheels publish, the command runs a microservice instance. It uses microservice.xml and environment.xml in the microservice folder.
    • If microservice folder is source project, one of the following happens:
      • if --no-publish is specified, the command runs microservice right from the source folder; no files are copied (more details below). This mode is for F5 debugging in Visual Studio.
      • if --no-publish is not specified, the command first internally performs nwheels publish to ./bin/microservice.working under the source folder, using environment.xml in the source folder. Then it runs microservice instance from the newly published location.

Options:

  • --no-publish - this mode is for F5 debugging in Visual Studio. When specified, the microservice runs right from projects binary folders; no files are copied. Modules and dependency assemblies are automatically located, and loaded from where they were compiled or restored. WARNING: limitations apply to solution structure, see below. This option can only be used when microservice folder is source project.

  • --project-config name - optional. Specifies project configuration name (e.g. Debug or Release) for dotnet publish, when it compiles the projects. If not specified, the default is Debug. This option cannot be specified with --no-publish, or when microservice folder is publish folder.

Sample debug configuration in Visual Studio

To debug a sample microservice in the NWheels solution, choose NWheels.Cli as the startup project, and in its project Debug settings, define:

  • Launch: Project
  • Application arguments: run NWheels.Samples.FirstHappyPath --no-publish
  • Working directory: $(SolutionDir)

Corresponding contents of launchSettings.json:

{
  "profiles": {
    "NWheels.Cli": {
      "commandName": "Project",
      "commandLineArgs": "run NWheels.Samples.FirstHappyPath --no-publish",
      "workingDirectory": "$(SolutionDir)"
    }
  }
}

Running sample microservice from command line

After compiling the solution, open command line and change current directory to /path/to/your/repo/Source.

Run microservice directly from sources:

dotnet NWheels.Cli\bin\Debug\netcoreapp1.1\nwheels.dll run NWheels.Samples.FirstHappyPath --no-publish

Publish microservice:

dotnet NWheels.Cli\bin\Debug\netcoreapp1.1\nwheels.dll publish NWheels.Samples.FirstHappyPath 

Run microservice from published folder:

dotnet NWheels.Cli\bin\Debug\netcoreapp1.1\nwheels.dll run NWheels.Samples.FirstHappyPath\bin\microservice.working

Current limitations

All in NWheels solution

In general, real end users will use NWheels through NuGet packages, and their applications will of course reside in their own separate solutions. This use case will be supported as soon as there will be published NuGet packages; and a few small changes may be required.

Until then, application projects must reside in the same solution (and under the same parent folder -- see below) with NWheels projects.. That is, by now it is possible to run sample applications that are part of the NWheels solution.

Solution structure

In a correct implementation, solution file should be inspected in order to determine location of module projects. Unfortunately, Roslyn project system cannot yet be fully consumed from a .NET Core application. I prefer to avoid implementation of my own inspection logic -- I'd rather wait for the Roslyn project system to be fully ported.

In the meanwhile, in order for the CLI to work, NWheels solution must be structured as follows:

  • module projects use default assembly names (equal to .CSPROJ file name)
  • all project folders are named after project files (but without the .CSPROJ extension)
  • all project folders reside in the solution folder

Implementation details

Publish

In order to publish a microservice, dotnet publish command is run on every module defined in microservice.xml, including the injection adapter.

This implementation will require a small change to support end-user solutions:

  • only modules defined under ApplicationModules element will need dontet publish.

Run from sources

In order to run from sources (nwheels run --no-publish), the BootConfiguration class was extended with an additional property of type AssemblyLocationMap. This new class encapsulates exact locations and lookup folders for module assemblies and their dependencies.

MicroserviceHost uses AssemblyLoadContext to load module assemblies. For resolution of dependency assemblies, a handler was attached to AssemblyLoadContext.Resolving event. This handler looks up assembly location in the AssemblyLocationMap, which was passed to MicroserviceHost inside the BootConfiguration object, during initialization.

nwheels run --no-publish populates AssemblyLocationMap with all relevant assemblies and lookup folders. For that, it uses ResolvePublishAssemblies MSBuild task.

It wasn't clear how to consume the task in-process (as opposed to full .NET Framework where this use case is fully supported), I didn't find the right combination of NuGet packages under .NET Core. Instead, I invoke dotnet msbuild command, with temporary generated .proj file, which invokes the task. The command produces parser-friendly output, which is used for populating AssemblyLocationMap. The map is then passed to MicroserviceHost inside BootConfiguration.

@felix-b felix-b closed this as completed Apr 5, 2017
@felix-b felix-b added the gen1 label Mar 29, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants