Skip to content

Commit afff218

Browse files
Add dotnet tool project Dax.Vpax.CLI (#124)
* Add dotnet tool project Dax.Vpax.CLI * Add release pipeline * Update README.md
1 parent 7436639 commit afff218

16 files changed

+301
-1
lines changed
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: release-vpax-cli
2+
on:
3+
workflow_dispatch:
4+
env:
5+
PROJECT: Dax.Vpax.CLI
6+
jobs:
7+
release:
8+
if: github.ref == 'refs/heads/master'
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: checkout
12+
uses: actions/checkout@v4
13+
with:
14+
fetch-depth: 0
15+
- name: nbgv
16+
uses: dotnet/[email protected]
17+
id: nbgv
18+
with:
19+
path: src/${{ env.PROJECT }}
20+
- name: dotnet pack
21+
run: dotnet pack 'src/${{ env.PROJECT }}/${{ env.PROJECT }}.csproj' -c Release -o .
22+
- shell: bash
23+
name: nuget push
24+
run: dotnet nuget push './${{ env.PROJECT }}.${{ steps.nbgv.outputs.NuGetPackageVersion }}.nupkg' -k "$NUGET_API_KEY" -s https://api.nuget.org/v3/index.json
25+
env:
26+
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}

VertiPaq-Analyzer-CLI.sln

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.1.32414.318
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dax.Vpax.CLI", "src\Dax.Vpax.CLI\Dax.Vpax.CLI.csproj", "{30E4369E-17E4-4E04-B7AC-12B4520EA0E3}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{30E4369E-17E4-4E04-B7AC-12B4520EA0E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{30E4369E-17E4-4E04-B7AC-12B4520EA0E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{30E4369E-17E4-4E04-B7AC-12B4520EA0E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{30E4369E-17E4-4E04-B7AC-12B4520EA0E3}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {BF417AA2-7521-408B-A3F5-A20CC760AE6E}
24+
EndGlobalSection
25+
EndGlobal

src/Dax.Metadata/Dax.Metadata.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<ItemGroup>
1919
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
2020
<PackageReference Include="Newtonsoft.Json" />
21+
<None Include="..\..\README.md" Pack="true" PackagePath="\" Visible="false" />
2122
</ItemGroup>
2223

2324
<PropertyGroup Condition="'$(TargetFramework)' == 'net462'">

src/Dax.Model.Extractor/Dax.Model.Extractor.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
<ItemGroup>
1515
<ProjectReference Include="..\Dax.Metadata\Dax.Metadata.csproj" />
16+
<None Include="..\..\README.md" Pack="true" PackagePath="\" Visible="false" />
1617
</ItemGroup>
1718

1819
<PropertyGroup>

src/Dax.ViewModel/Dax.ViewModel.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
<ItemGroup>
1515
<ProjectReference Include="..\Dax.Metadata\Dax.Metadata.csproj" />
16+
<None Include="..\..\README.md" Pack="true" PackagePath="\" Visible="false" />
1617
</ItemGroup>
1718

1819
<PropertyGroup>

src/Dax.ViewVpaExport/Dax.ViewVpaExport.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
<ItemGroup>
1515
<ProjectReference Include="..\Dax.Metadata\Dax.Metadata.csproj" />
16+
<None Include="..\..\README.md" Pack="true" PackagePath="\" Visible="false" />
1617
</ItemGroup>
1718

1819
<PropertyGroup>
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.CommandLine;
2+
using static Dax.Vpax.CLI.Commands.ExportCommandOptions;
3+
4+
namespace Dax.Vpax.CLI.Commands;
5+
6+
internal static class ExportCommand
7+
{
8+
private static readonly ExportCommandHandler s_handler = new();
9+
10+
internal static Command GetCommand()
11+
{
12+
var command = new Command("export", "Export a VPAX file from a tabular model")
13+
{
14+
PathArgument,
15+
ConnectionStringArgument,
16+
OverwriteOption,
17+
ExcludeTomOption,
18+
ExcludeVpaOption
19+
};
20+
command.Handler = s_handler;
21+
return command;
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.CommandLine.Invocation;
2+
using System.CommandLine.IO;
3+
using Dax.Metadata;
4+
using Dax.Model.Extractor;
5+
using Dax.Vpax.Tools;
6+
using static Dax.Vpax.CLI.Commands.ExportCommandOptions;
7+
8+
namespace Dax.Vpax.CLI.Commands;
9+
10+
internal sealed class ExportCommandHandler : ICommandHandler
11+
{
12+
public int Invoke(InvocationContext context) => throw new NotSupportedException("Use InvokeAsync instead.");
13+
14+
public async Task<int> InvokeAsync(InvocationContext context)
15+
{
16+
// TODO: forward cancellation token to vertipaq-analyzer extractor
17+
var cancellationToken = context.GetCancellationToken();
18+
var extractorAppName = ThisAssembly.AssemblyName;
19+
var extractorAppVersion = ThisAssembly.AssemblyFileVersion;
20+
21+
var path = context.ParseResult.GetValueForArgument(PathArgument);
22+
var connectionString = context.ParseResult.GetValueForArgument(ConnectionStringArgument);
23+
var overwrite = context.ParseResult.GetValueForOption(OverwriteOption);
24+
var excludeTom = context.ParseResult.GetValueForOption(ExcludeTomOption);
25+
var excludeVpa = context.ParseResult.GetValueForOption(ExcludeVpaOption);
26+
27+
using var vpaxStream = new MemoryStream();
28+
29+
// TODO: improve logging and support platform-specific commands such as those used in Azure DevOps and GitHub
30+
context.Console.Out.Write("Extracting VPAX metadata...");
31+
{
32+
var daxModel = TomExtractor.GetDaxModel(
33+
connectionString: connectionString,
34+
applicationName: extractorAppName,
35+
applicationVersion: extractorAppVersion,
36+
readStatisticsFromData: true,
37+
sampleRows: 0, // RI violation sampling is not applicable to VPAX files
38+
analyzeDirectQuery: true,
39+
analyzeDirectLake: DirectLakeExtractionMode.Full
40+
);
41+
42+
var vpaModel = excludeVpa ? null : new ViewVpaExport.Model(daxModel);
43+
var tomDatabase = excludeTom ? null : TomExtractor.GetDatabase(connectionString);
44+
45+
VpaxTools.ExportVpax(vpaxStream, daxModel, vpaModel, tomDatabase);
46+
}
47+
context.Console.Out.WriteLine("done.");
48+
context.Console.Out.Write("Exporting VPAX file...");
49+
{
50+
var mode = overwrite ? FileMode.Create : FileMode.CreateNew;
51+
using var fileStream = new FileStream(path, mode, FileAccess.Write, FileShare.Read);
52+
await vpaxStream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
53+
}
54+
context.Console.Out.WriteLine("done.");
55+
56+
return context.ExitCode;
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.CommandLine;
2+
using System.Data.OleDb;
3+
4+
namespace Dax.Vpax.CLI.Commands;
5+
6+
internal static class ExportCommandOptions
7+
{
8+
public static readonly Argument<string> ConnectionStringArgument = new(
9+
name: "connection-string",
10+
description: "Connection string to the tabular model",
11+
parse: (result) =>
12+
{
13+
var connectionString = result.Tokens.Single().Value;
14+
15+
var builder = new OleDbConnectionStringBuilder(connectionString);
16+
if (!builder.ContainsKey("Initial Catalog"))
17+
result.ErrorMessage = "The connection string does not contain the 'Initial Catalog' property.";
18+
19+
return connectionString;
20+
});
21+
22+
public static readonly Argument<string> PathArgument = new(
23+
name: "path",
24+
description: "Path to write the VPAX file"
25+
);
26+
27+
public static readonly Option<bool> OverwriteOption = new(
28+
name: "--overwrite",
29+
getDefaultValue: () => false,
30+
description: "Overwrite the VPAX file if it already exists"
31+
);
32+
33+
public static readonly Option<bool> ExcludeTomOption = new(
34+
name: "--exclude-bim",
35+
getDefaultValue: () => false,
36+
description: "Exclude the BIM model (Model.bim) from the export"
37+
);
38+
39+
public static readonly Option<bool> ExcludeVpaOption = new(
40+
name: "--exclude-vpa",
41+
getDefaultValue: () => false,
42+
description: "Exclude the VPA model (DaxVpaView.json) from the export"
43+
);
44+
}

src/Dax.Vpax.CLI/Dax.Vpax.CLI.csproj

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<OutputType>Exe</OutputType>
6+
<Nullable>enable</Nullable>
7+
<LangVersion>10.0</LangVersion>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
<AnalysisMode>all</AnalysisMode>
10+
<AnalysisLevel>latest</AnalysisLevel>
11+
<SatelliteResourceLanguages>en-US</SatelliteResourceLanguages>
12+
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
13+
</PropertyGroup>
14+
15+
<PropertyGroup>
16+
<IsPackable>true</IsPackable>
17+
<PackAsTool>true</PackAsTool>
18+
<ToolCommandName>vpax</ToolCommandName>
19+
<PackageId>Dax.Vpax.CLI</PackageId>
20+
<PackageProjectUrl>https://github.com/sql-bi/VertiPaq-Analyzer/tree/master/src/Dax.Vpax.CLI</PackageProjectUrl>
21+
<Title>VertiPaq Analyzer CLI</Title>
22+
<Description>VertiPaq Analyzer Command Line Tool</Description>
23+
<DebugType>embedded</DebugType>
24+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
25+
<!-- override IncludeSymbols from Directory.Build.props -->
26+
<IncludeSymbols>false</IncludeSymbols>
27+
</PropertyGroup>
28+
29+
<ItemGroup>
30+
<PackageReference Include="Dax.Model.Extractor" Version="1.5.1" />
31+
<PackageReference Include="Dax.Vpax" Version="1.5.1" />
32+
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
33+
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.133" PrivateAssets="all" />
34+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="all" />
35+
</ItemGroup>
36+
37+
<ItemGroup>
38+
<None Include="README.md" Pack="true" PackagePath="\" />
39+
</ItemGroup>
40+
41+
</Project>

src/Dax.Vpax.CLI/Program.cs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.CommandLine;
2+
using Dax.Vpax.CLI.Commands;
3+
4+
namespace Dax.Vpax.CLI;
5+
6+
internal sealed class Program
7+
{
8+
public static async Task<int> Main(string[] args)
9+
=> await Build().InvokeAsync(args).ConfigureAwait(false);
10+
11+
private static RootCommand Build()
12+
{
13+
var command = new RootCommand("VertiPaq-Analyzer CLI");
14+
command.Name = "vpax"; // Name must match <ToolCommandName> in csproj
15+
command.AddCommand(ExportCommand.GetCommand());
16+
return command;
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"profiles": {
3+
"Dax.Vpax.CLI": {
4+
"commandName": "Project"
5+
}
6+
}
7+
}

src/Dax.Vpax.CLI/README.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Dax.Vpax.CLI
2+
3+
This is a .NET tool that provides CLI access to [VertiPaq-Analyzer](https://github.com/sql-bi/VertiPaq-Analyzer) functions.
4+
5+
Operations supported by this tool are:
6+
7+
- Export a VPAX file from a tabular model.
8+
9+
## How to install the tool
10+
11+
The tool can be installed using the following command:
12+
13+
````bash
14+
dotnet tool install Dax.Vpax.CLI --global
15+
````
16+
17+
## How to run the tool
18+
19+
````bash
20+
vpax export "C:\output\file.vpax" "Provider=MSOLAP;Data Source=<SERVER>;Initial Catalog=<DATABASE>"
21+
````
22+
23+
Use `vpax -?` or `vpax export -?` to learn more.
24+
25+
```
26+
Description:
27+
Export a VPAX file from a tabular model
28+
29+
Usage:
30+
vpax export <path> <connection-string> [options]
31+
32+
Arguments:
33+
<path> Path to write the VPAX file
34+
<connection-string> Connection string to the tabular model
35+
36+
Options:
37+
--overwrite Overwrite the VPAX file if it already exists [default: False]
38+
--exclude-bim Exclude the BIM model (Model.bim) from the export [default: False]
39+
--exclude-vpa Exclude the VPA model (DaxVpaView.json) from the export [default: False]
40+
-?, -h, --help Show help and usage information
41+
```

src/Dax.Vpax.CLI/version.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
3+
"version": "0.1-beta",
4+
"nugetPackageVersion": {
5+
"semVer": 2
6+
},
7+
"publicReleaseRefSpec": [
8+
"^refs/heads/master"
9+
],
10+
"pathFilters": [
11+
"./"
12+
]
13+
}

src/Dax.Vpax/Dax.Vpax.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<ItemGroup>
1515
<ProjectReference Include="..\Dax.Metadata\Dax.Metadata.csproj" />
1616
<ProjectReference Include="..\Dax.ViewVpaExport\Dax.ViewVpaExport.csproj" />
17+
<None Include="..\..\README.md" Pack="true" PackagePath="\" Visible="false" />
1718
</ItemGroup>
1819

1920
<PropertyGroup>

src/Directory.Build.props

-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040

4141
<ItemGroup Condition="'$(IsPackable)' == 'true'">
4242
<None Include="..\..\LICENSE.md" Pack="true" PackagePath="\" Visible="false" />
43-
<None Include="..\..\README.md" Pack="true" PackagePath="\" Visible="false" />
4443
<None Include="..\..\package-icon.png" Pack="true" PackagePath="\" Visible="false" />
4544
</ItemGroup>
4645

0 commit comments

Comments
 (0)