Skip to content

Commit

Permalink
Merge pull request #1 from maisiesadler/config
Browse files Browse the repository at this point in the history
Config
  • Loading branch information
maisiesadler authored May 5, 2021
2 parents 5999d0b + 28c6d86 commit 938aaf6
Show file tree
Hide file tree
Showing 14 changed files with 369 additions and 73 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/generate-diagrams.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
run: dotnet build -c Release

- name: Generate a NuGet package
run: dotnet run -p src/DepTree.Console applicationconfig.json ./src/DepTree/bin/Release/net5.0/DepTree.dll
run: dotnet run -p src/DepTree.Console --config applicationconfig.json -a ./src/DepTree/bin/Release/net5.0/DepTree.dll -i None

- uses: EndBug/add-and-commit@v7
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

createpackage:
build:
runs-on: ubuntu-latest
needs: create-tag
env:
Expand Down Expand Up @@ -61,7 +61,7 @@ jobs:
create-release:
name: Create Release
runs-on: ubuntu-latest
needs: [create-tag, createpackage]
needs: [create-tag, build]
steps:
- name: Checkout code
uses: actions/checkout@master
Expand Down
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ ARG ASSEMBLY_LOCATION
ENV ASSEMBLY_LOCATION=$ASSEMBLY_LOCATION
ARG APPLICATION_CONFIG_LOCATION
ENV APPLICATION_CONFIG_LOCATION=$APPLICATION_CONFIG_LOCATION
ARG ROOT_TYPE
ENV ROOT_TYPE=$ROOT_TYPE
ARG SKIP_TYPE
ENV SKIP_TYPE=$SKIP_TYPE
ARG ASSEMBLY_CONFIG_LOCATION
ENV ASSEMBLY_CONFIG_LOCATION=$ASSEMBLY_CONFIG_LOCATION
ARG INTERFACE_RESOLVER
ENV INTERFACE_RESOLVER=$INTERFACE_RESOLVER

FROM mcr.microsoft.com/dotnet/aspnet:5.0-focal
COPY --from=build-env /out .
Expand Down
55 changes: 53 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,56 @@
# Dependency Tree

Create a self contained assembly
Dependency Tree will load an assembly then, starting with the root type, recursively read the constructor parameters to create a tree of dependencies.

`dotnet publish -r osx.10.12-x64 -p:PublishSingleFile=true --self-contained true`
By default it uses the Startup file to load and resolve which type is registered to an interface.

## Using the github action

The GitHub action uses the [Dockerfile](./Dockerfile) in the root of the project.

| Name | Environment Variable | CLI setting | | Required |
| -- | -- | -- | -- | -- |
| Assembly Location | ASSEMBLY_LOCATION | -a --assembly | The location of the assembly to read | Yes |
| Root types | ROOT_TYPES | -t --root-types | The root type to use for the dependency tree, multiple values can be used as a csv input | Yes |
| Skip types | SKIP_TYPES | -s --skip-types | Types to skip, multiple values can be used as a csv input | No |
| Assembly Config Location | ASSEMBLY_CONFIG_LOCATION | -n --assembly-config | The location of the configuration file required to build IConfiguration for Startup | No |
| Interface Resolver | INTERFACE_RESOLVER | -i --interface-resolver | Method for resolving interfaces, Allowed Values: None, Startup. Default: Startup. | No |
| Application Config Location | APPLICATION_CONFIG_LOCATION | -n --assembly-config | The location of application config file | No |

### Application config

There is support for passing in a config file for multiple `Skip` or `Generate` (root) types.

[Example](./applicationconfig.json)

### Interface resolver

If `Interface Resolver` is `Startup` (default) then the application will look for a class named Startup.

If the Startup file has an IConfiguration constructor then set the `Assembly Config Location` to the path of an example config file that has enough data in it to build the configuration.

If there is no startup then set `Interface Resolver` to `None`.

### Example applications

#### Pokedex - dotnet5 web api

Needed to publish a single file application

`dotnet publish -r linux-x64 -p:PublishSingleFile=true -p:UseAppHost=true --self-contained`

[Example output](https://github.com/maisiesadler/pokedex/blob/main/DependencyTree.md)

<img src="http://yuml.me/diagram/scruffy/class/[PokemonController]-&gt;[BasicPokemonInformationRetriever], [PokemonController]-&gt;[TranslatedPokemonInformationRetriever], [PokemonController]-&gt;[ILogger`1], [BasicPokemonInformationRetriever]-&gt;[IPokemonQuery|PokemonQuery], [IPokemonQuery]-2&gt;[ICache`1], [IPokemonQuery]-2&gt;[IPokeApiClient], [TranslatedPokemonInformationRetriever]-&gt;[IPokemonQuery|PokemonQuery], [TranslatedPokemonInformationRetriever]-&gt;[ITranslationQuery|TranslationQuery], [ITranslationQuery]-&gt;[ICache`1], [ITranslationQuery]-&gt;[IFunTranslationsApiClient], [ITranslationQuery]-&gt;[ILogger`1]" />

[Example workflow](https://github.com/maisiesadler/pokedex/blob/main/.github/workflows/dependencytree.yml)

- Need to add RuntimeIdentifiers [here](https://github.com/maisiesadler/pokedex/blob/main/src/Pokedex/Pokedex.csproj#L5).

#### Endpoints - doesn't use Startup

[Example output](https://github.com/maisiesadler/Endpoints/blob/master/Dependencies.md)

<img src="http://yuml.me/diagram/scruffy/class/[MyModelRetriever]-&gt;[IDbThing]" />

[Example workflow action](https://github.com/maisiesadler/Endpoints/blob/master/.github/workflows/dependencytree.yml)
17 changes: 12 additions & 5 deletions run.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# dotnet build src/Example
dotnet run -p src/DepTree.Console \
applicationconfig.json \
/Users/maisiesadler/comparison/property-service/src/PropertyService.Api/bin/Release/net5.0/osx.10.12-x64/PropertyService.Api.dll \
/Users/maisiesadler/comparison/property-service/src/PropertyService.Test.Integration/testsettings.json
dotnet publish ./src/DepTree.Console/DepTree.Console.csproj -c Release -o out --no-self-contained

ASSEMBLY_LOCATION=/Users/maisiesadler/repos/endpoints/src/Endpoints.Api/bin/Release/net5.0/osx.10.12-x64/Endpoints.Api.dll
ROOT_TYPES=Pokedex.Controllers.PokemonController

ASSEMBLY_LOCATION=$ASSEMBLY_LOCATION \
APPLICATION_CONFIG_LOCATION=$APPLICATION_CONFIG_LOCATION \
ROOT_TYPES=$ROOT_TYPES \
SKIP_TYPES=$SKIP_TYPES \
ASSEMBLY_CONFIG_LOCATION=$ASSEMBLY_CONFIG_LOCATION \
INTERFACE_RESOLVER=$INTERFACE_RESOLVER \
dotnet out/DepTree.Console.dll
8 changes: 7 additions & 1 deletion runindocker.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
#!/bin/bash

result=$(dotnet /DepTree.Console.dll $APPLICATION_CONFIG_LOCATION $ASSEMBLY_LOCATION)
result=$(ASSEMBLY_LOCATION=$ASSEMBLY_LOCATION \
APPLICATION_CONFIG_LOCATION=$APPLICATION_CONFIG_LOCATION \
ROOT_TYPES=$ROOT_TYPES \
SKIP_TYPES=$SKIP_TYPES \
ASSEMBLY_CONFIG_LOCATION=$ASSEMBLY_CONFIG_LOCATION \
INTERFACE_RESOLVER=$INTERFACE_RESOLVER \
dotnet /DepTree.Console.dll)

r=$?
if [ $r -ne 0 ]; then
Expand Down
61 changes: 61 additions & 0 deletions src/DepTree.Console.Tests/ConfigurationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Xunit;
using DepTree.Console.Configuration;

namespace DepTree.Console.Tests
{
public class ConfigurationTests
{
[Fact]
public void CanBuildApplicationConfigFromArgs()
{
var args = new[] { "-a", "assembly-location", "-t", "type", "-c", "config-location" };

var (config, ok) = ApplicationConfiguration.Build(args);

Assert.NotNull(config);
var type = Assert.Single(config.Generate);
Assert.Equal("type", type);
Assert.Equal("assembly-location", config.AssemblyLocation);
}

[Fact]
public void InvalidAssemblyLocationProducesError()
{
var args = new[] { "-a", "assembly-location", "-t", "type", "-c", "config-location" };

var (config, ok) = ApplicationConfiguration.Build(args);

Assert.False(ok);
Assert.Contains("Assembly Location 'assembly-location' is missing or invalid", config.Errors);
}

[Fact]
public void ConfigFileIsParsed()
{
var args = new[] { "-a", "assembly-location", "-c", "exampleappconfig.json" };

var (config, ok) = ApplicationConfiguration.Build(args);

Assert.NotNull(config);
Assert.Equal(2, config.Generate.Count);
Assert.Equal("DepTree.DependencyTree", config.Generate[0]);
Assert.Equal("DepTree.AnotherRootType", config.Generate[1]);
var skip = Assert.Single(config.Skip);
Assert.Equal("Serilog.IDiagnosticContext", skip);
}

[Fact]
public void CsvTypesAreAdded()
{
var args = new[] { "-a", "assembly-location", "-t", "type1,type2" };

var (config, ok) = ApplicationConfiguration.Build(args);

Assert.NotNull(config);
Assert.Equal(2, config.Generate.Count);
Assert.Equal("type1", config.Generate[0]);
Assert.Equal("type2", config.Generate[1]);
Assert.Equal("assembly-location", config.AssemblyLocation);
}
}
}
30 changes: 30 additions & 0 deletions src/DepTree.Console.Tests/DepTree.Console.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="1.3.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DepTree.Console\DepTree.Console.csproj" />
</ItemGroup>

<ItemGroup>
<Content Include="exampleappconfig.json" CopyToOutputDirectory="Always" />
</ItemGroup>

</Project>
9 changes: 9 additions & 0 deletions src/DepTree.Console.Tests/exampleappconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Skip": [
"Serilog.IDiagnosticContext"
],
"Generate": [
"DepTree.DependencyTree",
"DepTree.AnotherRootType"
]
}
136 changes: 136 additions & 0 deletions src/DepTree.Console/Configuration/ApplicationConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using CommandLine;
using Microsoft.Extensions.Configuration;

namespace DepTree.Console.Configuration
{
public class ApplicationConfiguration
{
private ApplicationConfiguration() { }

private string _assemblyConfigLocation;
private string _configLocation;

public IConfiguration AssemblyConfiguration { get; private set; }
public string AssemblyLocation { get; private set; }
public string InterfaceResolver { get; private set; }
public HashSet<string> Skip { get; private set; } = new HashSet<string>();
public List<string> Generate { get; private set; } = new List<string>();
public List<string> Errors { get; private set; } = new List<string>();
public bool IsValid => Errors.Count == 0;

public static (ApplicationConfiguration, bool) Build(string[] args)
{
var config = new ApplicationConfiguration();

config.ReadEnvironmentVariables();

config.TryReadArgs(args);
config.TryReadFileConfig();

config.TryBuildAssemblyConfiguration();

config.Validate();

return (config, config.IsValid);
}

private void Validate()
{
if (string.IsNullOrEmpty(AssemblyLocation) || !File.Exists(AssemblyLocation))
{
Errors.Add($"Assembly Location '{AssemblyLocation}' is missing or invalid");
}
}

private void ReadEnvironmentVariables()
{
AssemblyLocation = Environment.GetEnvironmentVariable("ASSEMBLY_LOCATION");
_assemblyConfigLocation = Environment.GetEnvironmentVariable("ASSEMBLY_CONFIG_LOCATION");
_configLocation = Environment.GetEnvironmentVariable("APPLICATION_CONFIG_LOCATION");
InterfaceResolver = Environment.GetEnvironmentVariable("INTERFACE_RESOLVER");

var rootTypes = Environment.GetEnvironmentVariable("ROOT_TYPES");
if (!string.IsNullOrWhiteSpace(rootTypes))
{
var types = rootTypes.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
Generate.AddRange(types);
}

var skipTypes = Environment.GetEnvironmentVariable("SKIP_TYPES");
if (!string.IsNullOrWhiteSpace(skipTypes))
{
var skip = skipTypes.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
foreach (var s in skip) Skip.Add(s);
}
}

private void TryReadArgs(string[] args)
{
var parser = Parser.Default.ParseArguments<CommandLineInputs>(() => new(), args);
parser.WithParsed(inputs =>
{
if (!string.IsNullOrWhiteSpace(inputs.AssemblyLocation))
AssemblyLocation = inputs.AssemblyLocation;

if (!string.IsNullOrWhiteSpace(inputs.InterfaceResolver))
InterfaceResolver = inputs.InterfaceResolver;

if (!string.IsNullOrWhiteSpace(inputs.RootTypes))
{
var types = inputs.RootTypes.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
Generate.AddRange(types);
}

if (!string.IsNullOrWhiteSpace(inputs.SkipTypes))
{
var skip = inputs.SkipTypes.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
foreach (var s in skip) Skip.Add(s);
}

if (!string.IsNullOrWhiteSpace(inputs.AssemblyConfigLocation))
_assemblyConfigLocation = inputs.AssemblyConfigLocation;
if (!string.IsNullOrWhiteSpace(inputs.ConfigLocation))
_configLocation = inputs.ConfigLocation;
});

parser.WithNotParsed(errors =>
{
Errors.Add("Invalid CLI inputs");
});
}

private void TryReadFileConfig()
{
try
{
if (string.IsNullOrWhiteSpace(_configLocation)) return;
var configContents = File.ReadAllText(_configLocation);
var fileConfig = JsonSerializer.Deserialize<FileConfiguration>(configContents);

if (fileConfig.Skip != null)
foreach (var s in fileConfig.Skip) Skip.Add(s);

if (fileConfig.Generate != null)
Generate.AddRange(fileConfig.Generate);
}
catch (Exception ex)
{
System.Console.WriteLine($"Exception reading config: {ex.Message}.");
}
}

private void TryBuildAssemblyConfiguration()
{
if (!string.IsNullOrWhiteSpace(_assemblyConfigLocation) && File.Exists(_assemblyConfigLocation))
{
var cfgBuilder = new ConfigurationBuilder();
cfgBuilder.AddJsonFile(_assemblyConfigLocation, optional: false, reloadOnChange: false);
AssemblyConfiguration = cfgBuilder.Build();
}
}
}
}
Loading

0 comments on commit 938aaf6

Please sign in to comment.