diff --git a/.build/README.md b/.build/README.md new file mode 100644 index 0000000..7377947 --- /dev/null +++ b/.build/README.md @@ -0,0 +1 @@ +# Build scripts diff --git a/.build/definitions.cake b/.build/definitions.cake new file mode 100644 index 0000000..86028b6 --- /dev/null +++ b/.build/definitions.cake @@ -0,0 +1,185 @@ +// ADDINS +#addin nuget:?package=Cake.Coveralls&version=0.10.1 +#addin nuget:?package=Cake.FileHelpers&version=3.2.1 +#addin nuget:?package=Cake.Incubator&version=5.1.0 +#addin nuget:?package=Cake.Issues&version=0.8.1 +#addin nuget:?package=Cake.AppVeyor&version=4.0.0 + +// TOOLS +#tool nuget:?package=GitReleaseManager&version=0.10.3 +#tool nuget:?package=GitVersion.CommandLine&version=5.1.3 +#tool nuget:?package=coveralls.io&version=1.4.2 +#tool nuget:?package=OpenCover&version=4.7.922 +#tool nuget:?package=ReportGenerator&version=4.5.0 + + +public class CodeCoverageSettings +{ + public string ExcludeByFile { get; set; } = "*/*Designer.cs"; + public string ExcludeByAttribute { get; set; } = "*.ExcludeFromCodeCoverage*"; + public string ExcludeFilter { get; set; } = "-[Tests*]*"; + public string IncludeFilter { get; set; } +} + +// params +public class ProjectSettings { + public string RepoOwner { get; set; } + public string RepoName { get; set; } + public string SolutionName { get; set; } + + public CodeCoverageSettings CodeCoverage { get; } + + public ProjectSettings(string repoOwner, string repoName, string solutionName) + { + if (string.IsNullOrEmpty(repoOwner)) + throw new ArgumentNullException(nameof(repoOwner), "Value cannot be null or empty."); + if (string.IsNullOrEmpty(repoName)) + throw new ArgumentNullException(nameof(repoName), "Value cannot be null or empty."); + if (string.IsNullOrEmpty(solutionName)) + throw new ArgumentNullException(nameof(solutionName), "Value cannot be null or empty."); + + RepoOwner = repoOwner; + RepoName = repoName; + SolutionName = solutionName; + + CodeCoverage = new CodeCoverageSettings { + IncludeFilter = $"+[solutionName*]*" + }; + } +} + +public class Credentials { + public string UserName { get; } + public string Password { get; } + + public Credentials(string userName, string password) { + UserName = userName; + Password = password; + } +} + +public class BuildVersion { + public string NuGet { get; } + public string Full { get; } + public string Informational { get; } + public string NextMajor { get; } + public string CommitHash { get; } + public string Milestone { get; } + + public BuildVersion(string nuget, string full, string informational, string nextMajor, string commitHash, string milestone) { + NuGet = nuget; + Full = full; + Informational = informational; + NextMajor = nextMajor; + CommitHash = commitHash; + Milestone = milestone; + } +} + +public class RepositoryInfo { + public bool IsPullRequest { get; protected set; } + public bool IsMain { get; protected set; } + public bool IsDevelopBranch { get; protected set; } + // Release or hotfix branch + public bool IsReleaseBranch { get; protected set; } + public bool IsTagged { get; protected set; } + + public static RepositoryInfo Get(BuildSystem buildSystem, ProjectSettings settings) { + return new RepositoryInfo { + IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest, + IsDevelopBranch = StringComparer.OrdinalIgnoreCase.Equals("develop", buildSystem.AppVeyor.Environment.Repository.Branch), + IsReleaseBranch = buildSystem.AppVeyor.Environment.Repository.Branch.IndexOf("releases/", StringComparison.OrdinalIgnoreCase) >= 0 + || buildSystem.AppVeyor.Environment.Repository.Branch.IndexOf("hotfixes/", StringComparison.OrdinalIgnoreCase) >= 0, + IsTagged = buildSystem.AppVeyor.Environment.Repository.Tag.IsTag, + IsMain = StringComparer.OrdinalIgnoreCase.Equals($"{settings.RepoOwner}/{settings.RepoName}", buildSystem.AppVeyor.Environment.Repository.Name), + }; + } +} + +// default paths and files +public class Paths { + public DirectoryPath RootDir { get; } + public string SrcDir { get; set; } + public string ArtifactsDir {get; set; } + public string TestCoverageOutputFile { get; set; } + public string TestCoverageReportDir { get; set; } + public string PackagesDir { get; set; } + public string BuildPropsFile { get; set; } + public string TestsRootDir { get; set; } + public string SamplesRootDir { get; set; } + public string CommonAssemblyVersionFile { get; set; } + + public Paths(ICakeContext context) + { + RootDir = context.MakeAbsolute(context.Directory("./")); + SrcDir = "./src"; + ArtifactsDir = "./artifacts"; + TestCoverageOutputFile = ArtifactsDir + "/OpenCover.xml"; + TestCoverageReportDir = ArtifactsDir + "/CodeCoverageReport"; + PackagesDir = ArtifactsDir + "/packages"; + BuildPropsFile = SrcDir + "/Directory.Build.props"; + TestsRootDir = SrcDir + "/tests"; + CommonAssemblyVersionFile = SrcDir + "/common/AssemblyVersion.cs"; + } + +} + + +public class BuildInfo { + public string Target { get; protected set; } + public string Config { get; protected set; } + + public bool IsDebug { get; protected set; } + public bool IsRelease {get; protected set;} + + public bool IsLocal { get; protected set; } + public string AppVeyorJobId { get; protected set; } + + public BuildVersion Version { get; protected set; } + + public RepositoryInfo Repository { get; protected set; } + + public string GitHubToken { get; protected set; } + + public Paths Paths { get; protected set; } + + public ProjectSettings Settings { get; protected set; } + + public static BuildInfo Get(ICakeContext context, ProjectSettings settings) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + var target = context.Argument("target", "Default"); + var config = context.Argument("buildConfig", "Release"); + var buildSystem = context.BuildSystem(); + + // Calculate version and commit hash + GitVersion semVersion = context.GitVersion(); + var version = new BuildVersion( + semVersion.NuGetVersion, + semVersion.FullBuildMetaData, + semVersion.InformationalVersion, + $"{semVersion.Major+1}.0.0", + semVersion.Sha, + semVersion.MajorMinorPatch + ); + + var gitHubToken = context.EnvironmentVariable("GITHUB_TOKEN"); + + return new BuildInfo { + Target = target, + Config = config, + IsDebug = string.Equals(config, "Debug", StringComparison.OrdinalIgnoreCase), + IsRelease = string.Equals(config, "Release", StringComparison.OrdinalIgnoreCase), + IsLocal = buildSystem.IsLocalBuild, + AppVeyorJobId = buildSystem.AppVeyor.Environment.JobId, + Version = version, + Repository = RepositoryInfo.Get(buildSystem, settings), + GitHubToken = gitHubToken, + Settings = settings, + Paths = new Paths(context), + }; + } +} + + diff --git a/.build/tasks.cake b/.build/tasks.cake new file mode 100644 index 0000000..8ef604e --- /dev/null +++ b/.build/tasks.cake @@ -0,0 +1,217 @@ +// TASKS + +Task("CleanAll") + .Does(build => + { + CleanDirectories($"{build.Paths.SrcDir}/**/obj"); + CleanDirectories($"{build.Paths.SrcDir}/**/bin"); + CleanDirectories($"{build.Paths.ArtifactsDir}/**"); + }); + +Task("SetVersion") + .Does(build => + { + CreateAssemblyInfo(build.Paths.CommonAssemblyVersionFile, new AssemblyInfoSettings + { + FileVersion = build.Version.Milestone, + InformationalVersion = build.Version.Informational, + Version = build.Version.Milestone + }); + }); + + +Task("UpdateAppVeyorBuildNumber") + .WithCriteria(() => AppVeyor.IsRunningOnAppVeyor) + .ContinueOnError() + .Does(build => + { + AppVeyor.UpdateBuildVersion(build.Version.Full); + }); + + +Task("Restore") + .Does(build => + { + DotNetCoreRestore(build.Paths.SrcDir); + }); + + +Task("RunXunitTests") + .Does(build => + { + var projectPath = build.Paths.SrcDir; + var projectFilename = build.Settings.SolutionName; + Information("Calculating code coverage for {0} ...", projectFilename); + + Func buildProcessArgs = (buildCfg) => { + var pb = new ProcessArgumentBuilder() + .AppendSwitch("--configuration", buildCfg) + .AppendSwitch("--filter", "Category!=ManualTests") + .AppendSwitch("--results-directory", build.Paths.RootDir.Combine(build.Paths.ArtifactsDir).FullPath) + .Append("--no-restore") + .Append("--no-build"); + if (!build.IsLocal) { + pb.AppendSwitch("--test-adapter-path", ".") + .AppendSwitch("--logger", "AppVeyor"); + } + else { + pb.AppendSwitch("--logger", $"trx;LogFileName={projectFilename}.trx"); + } + return pb; + }; + + var openCoverSettings = new OpenCoverSettings + { + OldStyle = true, + ReturnTargetCodeOffset = 0, + ArgumentCustomization = args => args.Append("-mergeoutput").Append("-hideskipped:File;Filter;Attribute"), + WorkingDirectory = projectPath, + } + .WithFilter($"{build.Settings.CodeCoverage.IncludeFilter} {build.Settings.CodeCoverage.ExcludeFilter}") + .ExcludeByAttribute(build.Settings.CodeCoverage.ExcludeByAttribute) + .ExcludeByFile(build.Settings.CodeCoverage.ExcludeByFile); + + // run open cover for debug build configuration + OpenCover( + tool => tool.DotNetCoreTool( + projectPath.ToString(), + "test", + buildProcessArgs("Debug") + ), + build.Paths.RootDir.Combine(build.Paths.TestCoverageOutputFile).FullPath, + openCoverSettings + ); + + // run tests again if Release mode was requested + if (build.IsRelease) + { + var solutionFullPath = build.Paths.RootDir.Combine(build.Paths.SrcDir).Combine(build.Settings.SolutionName) + ".sln"; + Information("Running Release mode tests for {0}", projectFilename); + DotNetCoreTool( + solutionFullPath, + "test", + buildProcessArgs("Release") + ); + } + }) + .DeferOnError(); + +Task("CleanPreviousTestResults") + .Does(build => + { + if (FileExists(build.Paths.TestCoverageOutputFile)) + DeleteFile(build.Paths.TestCoverageOutputFile); + DeleteFiles(build.Paths.ArtifactsDir + "/*.trx"); + if (DirectoryExists(build.Paths.TestCoverageReportDir)) + DeleteDirectory(build.Paths.TestCoverageReportDir, recursive: true); + }); + +Task("GenerateCoverageReport") + .WithCriteria((ctx, build) => build.IsLocal) + .Does(build => + { + ReportGenerator(build.Paths.TestCoverageOutputFile, build.Paths.TestCoverageReportDir); + }); + +Task("UploadCoverage") + .WithCriteria((ctx, build) => !build.IsLocal) + .Does(build => + { + CoverallsIo(build.Paths.TestCoverageOutputFile); + }); + +Task("RunUnitTests") + .IsDependentOn("Build") + .IsDependentOn("CleanPreviousTestResults") + .IsDependentOn("RunXunitTests") + .IsDependentOn("GenerateCoverageReport") + .IsDependentOn("UploadCoverage") + .Does(build => + { + Information("Done Test"); + }); + +Task("UpdateReleaseNotesLink") + .WithCriteria((ctx, build) => build.Repository.IsTagged) + .Does(build => + { + var releaseNotesUrl = $"https://github.com/{build.Settings.RepoOwner}/{build.Settings.RepoName}/releases/tag/{build.Version.Milestone}"; + Information("Updating ReleaseNotes URL to '{0}'", releaseNotesUrl); + XmlPoke(build.Paths.BuildPropsFile, + "/Project/PropertyGroup[@Label=\"Package\"]/PackageReleaseNotes", + releaseNotesUrl + ); + }); + + +Task("Build") + .IsDependentOn("SetVersion") + .IsDependentOn("UpdateAppVeyorBuildNumber") + .IsDependentOn("UpdateReleaseNotesLink") + .IsDependentOn("Restore") + .Does(build => + { + if (build.IsRelease) { + Information("Running {0} build for code coverage", "Debug"); + // need Debug build for code coverage + DotNetCoreBuild(build.Paths.SrcDir, new DotNetCoreBuildSettings { + NoRestore = true, + Configuration = "Debug", + }); + } + Information("Running {0} build", build.Config); + DotNetCoreBuild(build.Paths.SrcDir, new DotNetCoreBuildSettings { + NoRestore = true, + Configuration = build.Config, + }); + }); + + +Task("CreateNugetPackages") + .Does(build => + { + DotNetCorePack(build.Paths.SrcDir, new DotNetCorePackSettings { + Configuration = build.Config, + OutputDirectory = build.Paths.PackagesDir, + NoRestore = true, + NoBuild = true, + ArgumentCustomization = args => args.Append($"-p:Version={build.Version.NuGet}") + }); + }); + +Task("CreateRelease") + .WithCriteria((ctx, build) => + build.Repository.IsMain && build.Repository.IsReleaseBranch && build.Repository.IsPullRequest == false) + .Does(build => + { + GitReleaseManagerCreate( + build.GitHubToken, + build.Settings.RepoOwner, build.Settings.RepoName, + new GitReleaseManagerCreateSettings { + Milestone = build.Version.Milestone, + TargetCommitish = "master" + }); + }); + +Task("CloseMilestone") + .WithCriteria((ctx, build) => + build.Repository.IsMain && build.Repository.IsTagged && build.Repository.IsPullRequest == false) + .Does(build => + { + GitReleaseManagerClose( + build.GitHubToken, + build.Settings.RepoOwner, build.Settings.RepoName, + build.Version.Milestone + ); + }); + +Task("Default") + .IsDependentOn("UpdateAppVeyorBuildNumber") + .IsDependentOn("Build") + .IsDependentOn("RunUnitTests") + .IsDependentOn("CreateNugetPackages") + .IsDependentOn("CreateRelease") + .IsDependentOn("CloseMilestone") + .Does( + () => {} + ); diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d845a26 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,93 @@ +# Suppress: EC112 +root=true + +[*.proto] +indent_style=tab +indent_size=tab +tab_width=4 + +[*.{asax,ascx,aspx,cs,cshtml,css,htm,html,js,jsx,master,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}] +indent_style=space +indent_size=4 +tab_width=4 + +[*.{appxmanifest,build,config,csproj,dbml,discomap,dtd,json,jsproj,lsproj,njsproj,nuspec,proj,props,resjson,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}] +indent_style=space +indent_size=2 +tab_width=2 + +[*] + +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers=false +csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion +dotnet_style_predefined_type_for_locals_parameters_members=true:hint +dotnet_style_predefined_type_for_member_access=true:hint +dotnet_style_qualification_for_event=false:warning +dotnet_style_qualification_for_field=false:warning +dotnet_style_qualification_for_method=false:warning +dotnet_style_qualification_for_property=false:warning +dotnet_style_require_accessibility_modifiers=for_non_interface_members:hint + +# ReSharper properties +resharper_add_imports_to_deepest_scope=true +resharper_align_multiline_binary_expressions_chain=false +resharper_allow_comment_after_lbrace=true +resharper_apply_on_completion=true +resharper_braces_for_fixed=required_for_multiline +resharper_braces_for_for=required_for_multiline_statement +resharper_braces_for_foreach=required_for_multiline +resharper_braces_for_using=required_for_multiline +resharper_braces_for_while=required_for_multiline +resharper_csharp_blank_lines_around_type=2 +resharper_csharp_insert_final_newline=true +resharper_csharp_keep_blank_lines_in_code=1 +resharper_csharp_keep_blank_lines_in_declarations=1 +resharper_csharp_max_line_length=150 +resharper_csharp_wrap_after_declaration_lpar=true +resharper_indent_nested_fixed_stmt=true +resharper_indent_nested_foreach_stmt=true +resharper_indent_nested_for_stmt=true +resharper_indent_nested_while_stmt=true +resharper_js_keep_blank_lines_in_code=1 +resharper_keep_existing_embedded_arrangement=false +resharper_local_function_body=expression_body +resharper_place_accessorholder_attribute_on_same_line=False +resharper_place_constructor_initializer_on_same_line=false +resharper_prefer_explicit_discard_declaration=true +resharper_wrap_multiple_type_parameter_constraints_style=chop_always +resharper_xmldoc_linebreaks_inside_tags_for_elements_longer_than=100 + +# ReSharper inspection severities +resharper_local_suppression_highlighting=none +resharper_missing_annotation_highlighting=none +resharper_redundant_argument_default_value_highlighting=none +resharper_suggest_var_or_type_built_in_types_highlighting=none +resharper_suggest_var_or_type_elsewhere_highlighting=none +resharper_suggest_var_or_type_simple_types_highlighting=none +resharper_use_string_interpolation_highlighting=none + + +[**/tests/**.cs] +resharper_async_converter_async_await_may_be_elided_highlighting=do_not_show +resharper_class_never_instantiated_global_highlighting=do_not_show +resharper_consider_using_configure_await_highlighting=do_not_show +resharper_event_exception_not_documented_highlighting=do_not_show +resharper_exception_not_documented_highlighting=do_not_show +resharper_exception_not_documented_optional_highlighting=do_not_show +resharper_heap_view_boxing_allocation_highlighting=do_not_show +resharper_heap_view_closure_allocation_highlighting=do_not_show +resharper_heap_view_delegate_allocation_highlighting=do_not_show +resharper_heap_view_object_allocation_evident_highlighting=do_not_show +resharper_inconsistent_naming_highlighting=do_not_show +resharper_missing_xml_doc_highlighting=do_not_show +resharper_missing_xmldoc_highlighting=do_not_show +resharper_private_field_can_be_converted_to_local_variable_highlighting=do_not_show +resharper_return_value_of_pure_method_is_not_used_highlighting=do_not_show + +[**/{Startup,Program}.cs] +resharper_event_exception_not_documented_highlighting=do_not_show +resharper_exception_not_documented_highlighting=do_not_show +resharper_exception_not_documented_optional_highlighting=do_not_show +resharper_heap_view_object_allocation_evident_highlighting=do_not_show +resharper_missing_xml_doc_highlighting=do_not_show diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index 3e759b7..4f2ba2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,26 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo *.user -*.userosscache *.sln.docstates -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ -[Rr]eleases/ x64/ -x86/ +build/ bld/ [Bb]in/ [Oo]bj/ -[Ll]og/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -# NUNIT +#NUNIT *.VisualState.xml TestResult.xml @@ -45,29 +29,14 @@ TestResult.xml [Rr]eleasePS/ dlldata.c -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ -**/Properties/launchSettings.json - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio *_i.c *_p.c *_i.h *.ilk *.meta *.obj -*.iobj *.pch *.pdb -*.ipdb *.pgc *.pgd *.rsp @@ -77,7 +46,6 @@ StyleCopReport.xml *.tlh *.tmp *.tmp_proj -*.log *.vspscc *.vssscc .builds @@ -92,21 +60,14 @@ _Chutzpah* ipch/ *.aps *.ncb -*.opendb *.opensdf *.sdf *.cachefile -*.VC.db -*.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx -*.sap - -# Visual Studio Trace Files -*.e2e # TFS 2012 Local Workspace $tf/ @@ -119,7 +80,7 @@ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user -# JustCode is a .NET coding add-in +# JustCode is a .NET coding addin-in .JustCode # TeamCity is a build add-in @@ -128,18 +89,10 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - # NCrunch +*.ncrunch* _NCrunch_* .*crunch*.local.xml -nCrunchTemp_* # MightyMoose *.mm.* @@ -167,164 +120,61 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output + +# NuGet Packages Directory +packages/ +## TODO: If the tool you use requires repositories.config uncomment the next line +#!packages/repositories.config + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +# This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) +!packages/build/ + +# Windows Azure Build Output csx/ *.build.csdef -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files +# Windows Store app package directory AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ # Others +sql/ +*.Cache ClientBin/ +[Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview -*.jfm *.pfx *.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ +node_modules/ # RIA/Silverlight projects Generated_Code/ -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak # SQL Server files *.mdf *.ldf -*.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings -*.rptproj.rsuser # Microsoft Fakes FakesAssemblies/ -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ +.vs/ +/output +/out +/artifacts +/src/common/AssemblyVersion.cs +/tools diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a18b864 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,25 @@ +Contributing +============ + +## Pull requests + +- Add XML documentation to all public members. +- Add unit-tests covering functionality implemented (unless this is trivial fix or logic was not changed). +- Make sure all tests are passing. +- Squash commits. Ideally it should be 1 commit per feature implemented or bug fixed). If there are several features/bugs +addressed by pull request, consider creating several PRs. +- Create pull request against **develop** branch. + + +## Release process + +* Release notes are generated based on closed issues assigned to milestone `version` + +- Create `releases/version` branch of `develop` +- Prepare for release -> (produces beta- versions) + - Make sure all issues included in release are closed and labelled correctly +- Merge `releases/version` to `master` +- Tag `master` with version -> produces release version +- Update `develop` branch: `git pull --rebase origin master` +- Remove `releases/version` branch +- Review and publish release on GitHub - this manual step to review release notes before publishing diff --git a/GitReleaseManager.yaml b/GitReleaseManager.yaml new file mode 100644 index 0000000..675edc6 --- /dev/null +++ b/GitReleaseManager.yaml @@ -0,0 +1,69 @@ +create: + include-footer: true + footer-heading: Where to get it + footer-content: You can download this release from [nuget.org](https://www.nuget.org/packages/dotnet-readbin/{milestone}) + footer-includes-milestone: true + milestone-replace-text: '{milestone}' + +export: + include-created-date-in-title: true + created-date-string-format: MMMM dd, yyyy + perform-regex-removal: true + regex-text: '### Where to get it(\r\n)*You can .*\)' + multiline-regex: true + +issue-labels-include: + - breaking change + - bug + - build + - dependencies + - documentation + - duplicate + - enhancement + - feature + - sample + - question + - wontfix + +issue-labels-exclude: + - 0 - Backlog + - 1 - Ready + - 2 - Working + - 3 - Review + - 4 - Done + - blocked + - invalid + - help-wanted + - no-response + - question + - ready + - wontfix + +issue-labels-alias: + - name: breaking change + header: Breaking Change + plural: Breaking Changes + - name: bug + header: Bug fixed + plural: Bugs fixed + - name: dependencies + header: Dependency upgraded + plural: Dependencies upgraded + - name: documentation + header: Documentation + plural: Documentation + - name: duplicate + header: Duplicate issue closed + plural: Duplicate issues closed + - name: enhancement + header: Enhancement + plural: Enhancements + - name: feature + header: New feature + plural: New features + - name: sample + header: Sample + plural: Samples + - name: build + header: Build improvement + plural: Build improvements diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..69d60cd --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,6 @@ +assembly-versioning-scheme: MajorMinorPatch +mode: ContinuousDelivery +branches: {} +ignore: + sha: [] +merge-message-formats: {} diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ef22a5 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# DOTNET-READBIN + +Reads and dump in human-readable format binary serialized data. + +Currently following formats are supported: +* BSON +* MessagePack +* HEX +* Base64 + + +## Build status + +||Stable|Pre-release| +|:--:|:--:|:--:| +|Build|[![Master](https://ci.appveyor.com/api/projects/status/viuo3401uolgsmg9/branch/master?svg=true)](https://ci.appveyor.com/project/shatl/dotnet-readbin/branch/master) | [![Dev branch](https://ci.appveyor.com/api/projects/status/viuo3401uolgsmg9/branch/develop?svg=true)](https://ci.appveyor.com/project/shatl/dotnet-readbin/branch/develop) | +|NuGet|[![NuGet](https://img.shields.io/nuget/v/dotnet-readbin.svg)](https://www.nuget.org/packages/dotnet-readbin) | [![NuGet](https://img.shields.io/nuget/vpre/dotnet-readbin.svg)](https://www.nuget.org/packages/dotnet-readbin/absoluteLatest) | +|Code coverage|[![Master](https://coveralls.io/repos/github/alphacloud/dotnet-readbin/badge.svg?branch=master)](https://coveralls.io/github/alphacloud/dotnet-readbin?branch=master) | [![Dev](https://coveralls.io/repos/github/alphacloud/dotnet-readbin/badge.svg?branch=develop)](https://coveralls.io/github/alphacloud/dotnet-readbin?branch=develop) | + + +# Installation + +Run `dotnet tool install --global dotnet-readbin` to install. + +Run `dotnet tool update --global dotnet-readbin` to update to latest stable version. Also `--version` option can be used to install preview version. + + +# Getting help + +Run `dotnet readbin --help` to get help on commands available and common options. + +Run `dotnet readbin command --help` to get help on specific command. + + +# Examples + +Multi-step conversion: +1. Convert from Base64 to binary +2. Dump binary MsgPack data in human-readable form. + +``` +echo gqJJZAGlVmFsdWWoVmFsdWU6IDE= | dotnet-readbin base64 | dotnet-readbin msgpack +``` + +Output: +``` +{"Id":1,"Value":"Value: 1"} +``` + +# Developmen info + +Applicaton targets .NET Core 3.1 + +Libraries used: +* [Newtonsoft Json.Bson](https://github.com/JamesNK/Newtonsoft.Json.Bson) +* [MessagePack-CSharp](https://github.com/neuecc/MessagePack-CSharp) diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..8aaefc9 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,52 @@ +version: .{build} +clone_folder: c:\work\dotnet-readbin + +pull_requests: + do_not_increment_build_number: true + +nuget: + disable_publish_on_pr: true + +os: Visual Studio 2019 + +environment: + # Set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to stop wasting time caching packages + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + # Disable sending usage data to Microsoft + DOTNET_CLI_TELEMETRY_OPTOUT: true + coveralls_repo_token: + secure: 7aa9OTSJRpXsKrrpolpHLDpJatVclMkv5exfxK2MIaf1P2orWsYpJEfT7R1Pln+9 + CAKE_SETTINGS_SKIPVERIFICATION: true + GITHUB_TOKEN: + secure: SakrMxbclSjNzFQxv8sA35OhulfvNm9VqHBEOgQrebPaF/Bv7AmZRsT8/YEfSQED + +install: + - ps: ./dotnet-install.ps1 -Version 3.1.102 -InstallDir "C:\Program Files\dotnet" + +build_script: + - ps: ./build.ps1 + +test: off + +artifacts: + - path: artifacts/packages/*.nupkg + name: Nugets + +deploy: + - provider: NuGet + name: Pre release + api_key: + secure: 6mTfswrYx6hJYzdwupTCsUUjXg+I50iqf81rGl6Zly7XlJtjT+KshLk7pNmT3TCN + on: + branch: + - develop + - /release\/v.*/ + - /releases.*/ + - /hotfixes.*/ + + - provider: NuGet + name: Tagged release + api_key: + secure: 6mTfswrYx6hJYzdwupTCsUUjXg+I50iqf81rGl6Zly7XlJtjT+KshLk7pNmT3TCN + on: + appveyor_repo_tag: true diff --git a/build.cake b/build.cake new file mode 100644 index 0000000..9a88744 --- /dev/null +++ b/build.cake @@ -0,0 +1,49 @@ +// DEFAULTS + +#load "./.build/definitions.cake" + + +// ARGUMENTS + +var target = Argument("target", "Default"); + +ProjectSettings settings = new ProjectSettings( + repoOwner: "alphacloud", + repoName: "dotnet-readbin", + solutionName: "dotnet-readbin") +{ + CodeCoverage = + { + IncludeFilter = "+[ReadBin.Tests]*" + } +}; + + +// SETUP / TEARDOWN + +Setup(context => +{ + var buildInfo = BuildInfo.Get(context, settings); // settings must be declared in main file + + Information("Building version {0} (tagged: {1}, local: {2}, release branch: {3})...", buildInfo.Version.NuGet, + buildInfo.Repository.IsTagged, buildInfo.IsLocal, buildInfo.Repository.IsReleaseBranch); + CreateDirectory(buildInfo.Paths.ArtifactsDir); + CleanDirectory(buildInfo.Paths.ArtifactsDir); + + return buildInfo; +}); + +Teardown(context => +{ + // Executed AFTER the last task. +}); + + +// TASKS + +#load "./.build/tasks.cake" + + +// EXECUTION + +RunTarget(target); diff --git a/build.ps1 b/build.ps1 new file mode 100755 index 0000000..7f1f813 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,256 @@ +########################################################################## +# This is the Cake bootstrapper script for PowerShell. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +<# + +.SYNOPSIS +This is a Powershell script to bootstrap a Cake build. + +.DESCRIPTION +This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) +and execute your Cake build script with the parameters you provide. + +.PARAMETER Script +The build script to execute. +.PARAMETER Target +The build script target to run. +.PARAMETER Configuration +The build configuration to use. +.PARAMETER Verbosity +Specifies the amount of information to be displayed. +.PARAMETER ShowDescription +Shows description about tasks. +.PARAMETER DryRun +Performs a dry run. +.PARAMETER SkipToolPackageRestore +Skips restoring of packages. +.PARAMETER ScriptArgs +Remaining arguments are added here. + +.LINK +https://cakebuild.net + +#> + +[CmdletBinding()] +Param( + [string]$Script = "build.cake", + [string]$Target, + [string]$Configuration, + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity, + [switch]$ShowDescription, + [Alias("WhatIf", "Noop")] + [switch]$DryRun, + [switch]$SkipToolPackageRestore, + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$ScriptArgs +) + +# Attempt to set highest encryption available for SecurityProtocol. +# PowerShell will not set this by default (until maybe .NET 4.6.x). This +# will typically produce a message for PowerShell v2 (just an info +# message though) +try { + # Set TLS 1.2 (3072), then TLS 1.1 (768), then TLS 1.0 (192), finally SSL 3.0 (48) + # Use integers because the enumeration values for TLS 1.2 and TLS 1.1 won't + # exist in .NET 4.0, even though they are addressable if .NET 4.5+ is + # installed (.NET 4.5 is an in-place upgrade). + # PowerShell Core already has support for TLS 1.2 so we can skip this if running in that. + if (-not $IsCoreCLR) { + [System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 -bor 48 + } + } catch { + Write-Output 'Unable to set PowerShell to use TLS 1.2 and TLS 1.1 due to old .NET Framework installed. If you see underlying connection closed or trust errors, you may need to upgrade to .NET Framework 4.5+ and PowerShell v3' + } + +[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null +function MD5HashFile([string] $filePath) +{ + if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) + { + return $null + } + + [System.IO.Stream] $file = $null; + [System.Security.Cryptography.MD5] $md5 = $null; + try + { + $md5 = [System.Security.Cryptography.MD5]::Create() + $file = [System.IO.File]::OpenRead($filePath) + return [System.BitConverter]::ToString($md5.ComputeHash($file)) + } + finally + { + if ($file -ne $null) + { + $file.Dispose() + } + } +} + +function GetProxyEnabledWebClient +{ + $wc = New-Object System.Net.WebClient + $proxy = [System.Net.WebRequest]::GetSystemWebProxy() + $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials + $wc.Proxy = $proxy + return $wc +} + +Write-Host "Preparing to run build script..." + +if(!$PSScriptRoot){ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} + +$TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$ADDINS_DIR = Join-Path $TOOLS_DIR "Addins" +$MODULES_DIR = Join-Path $TOOLS_DIR "Modules" +$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" +$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" +$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" +$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" +$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" +$ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config" +$MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config" + +# Make sure tools folder exists +if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { + Write-Verbose -Message "Creating tools directory..." + New-Item -Path $TOOLS_DIR -Type Directory | Out-Null +} + +# Make sure that packages.config exist. +if (!(Test-Path $PACKAGES_CONFIG)) { + Write-Verbose -Message "Downloading packages.config..." + try { + $wc = GetProxyEnabledWebClient + $wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) + } catch { + Throw "Could not download packages.config." + } +} + +# Try find NuGet.exe in path if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Trying to find nuget.exe in PATH..." + $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } + $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 + if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { + Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." + $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName + } +} + +# Try download NuGet.exe if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Downloading NuGet.exe..." + try { + $wc = GetProxyEnabledWebClient + $wc.DownloadFile($NUGET_URL, $NUGET_EXE) + } catch { + Throw "Could not download NuGet.exe." + } +} + +# Save nuget.exe path to environment to be available to child processed +$env:NUGET_EXE = $NUGET_EXE +$env:NUGET_EXE_INVOCATION = if ($IsLinux -or $IsMacOS) { + "mono `"$NUGET_EXE`"" +} else { + "`"$NUGET_EXE`"" +} + +# Restore tools from NuGet? +if(-Not $SkipToolPackageRestore.IsPresent) { + Push-Location + Set-Location $TOOLS_DIR + + # Check for changes in packages.config and remove installed tools if true. + [string] $md5Hash = MD5HashFile $PACKAGES_CONFIG + if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or + ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { + Write-Verbose -Message "Missing or changed package.config hash..." + Get-ChildItem -Exclude packages.config,nuget.exe,Cake.Bakery | + Remove-Item -Recurse + } + + Write-Verbose -Message "Restoring tools from NuGet..." + + $NuGetOutput = Invoke-Expression "& $env:NUGET_EXE_INVOCATION install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occurred while restoring NuGet tools." + } + else + { + $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" + } + Write-Verbose -Message ($NuGetOutput | Out-String) + + Pop-Location +} + +# Restore addins from NuGet +if (Test-Path $ADDINS_PACKAGES_CONFIG) { + Push-Location + Set-Location $ADDINS_DIR + + Write-Verbose -Message "Restoring addins from NuGet..." + $NuGetOutput = Invoke-Expression "& $env:NUGET_EXE_INVOCATION install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occurred while restoring NuGet addins." + } + + Write-Verbose -Message ($NuGetOutput | Out-String) + + Pop-Location +} + +# Restore modules from NuGet +if (Test-Path $MODULES_PACKAGES_CONFIG) { + Push-Location + Set-Location $MODULES_DIR + + Write-Verbose -Message "Restoring modules from NuGet..." + $NuGetOutput = Invoke-Expression "& $env:NUGET_EXE_INVOCATION install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occurred while restoring NuGet modules." + } + + Write-Verbose -Message ($NuGetOutput | Out-String) + + Pop-Location +} + +# Make sure that Cake has been installed. +if (!(Test-Path $CAKE_EXE)) { + Throw "Could not find Cake.exe at $CAKE_EXE" +} + +$CAKE_EXE_INVOCATION = if ($IsLinux -or $IsMacOS) { + "mono `"$CAKE_EXE`"" +} else { + "`"$CAKE_EXE`"" +} + + +# Build Cake arguments +$cakeArguments = @("$Script"); +if ($Target) { $cakeArguments += "-target=$Target" } +if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } +if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } +if ($ShowDescription) { $cakeArguments += "-showdescription" } +if ($DryRun) { $cakeArguments += "-dryrun" } +$cakeArguments += $ScriptArgs + +# Start Cake +Write-Host "Running build script..." +Invoke-Expression "& $CAKE_EXE_INVOCATION $($cakeArguments -join " ")" +exit $LASTEXITCODE diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..b9e1252 --- /dev/null +++ b/build.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +########################################################################## +# This is the Cake bootstrapper script for Linux and OS X. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +# Define directories. +SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +TOOLS_DIR=$SCRIPT_DIR/tools +ADDINS_DIR=$TOOLS_DIR/Addins +MODULES_DIR=$TOOLS_DIR/Modules +NUGET_EXE=$TOOLS_DIR/nuget.exe +CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe +PACKAGES_CONFIG=$TOOLS_DIR/packages.config +PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum +ADDINS_PACKAGES_CONFIG=$ADDINS_DIR/packages.config +MODULES_PACKAGES_CONFIG=$MODULES_DIR/packages.config + +# Define md5sum or md5 depending on Linux/OSX +MD5_EXE= +if [[ "$(uname -s)" == "Darwin" ]]; then + MD5_EXE="md5 -r" +else + MD5_EXE="md5sum" +fi + +# Define default arguments. +SCRIPT="build.cake" +CAKE_ARGUMENTS=() + +# Parse arguments. +for i in "$@"; do + case $1 in + -s|--script) SCRIPT="$2"; shift ;; + --) shift; CAKE_ARGUMENTS+=("$@"); break ;; + *) CAKE_ARGUMENTS+=("$1") ;; + esac + shift +done + +# Make sure the tools folder exist. +if [ ! -d "$TOOLS_DIR" ]; then + mkdir "$TOOLS_DIR" +fi + +# Make sure that packages.config exist. +if [ ! -f "$TOOLS_DIR/packages.config" ]; then + echo "Downloading packages.config..." + curl -Lsfo "$TOOLS_DIR/packages.config" https://cakebuild.net/download/bootstrapper/packages + if [ $? -ne 0 ]; then + echo "An error occurred while downloading packages.config." + exit 1 + fi +fi + +# Download NuGet if it does not exist. +if [ ! -f "$NUGET_EXE" ]; then + echo "Downloading NuGet..." + curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe + if [ $? -ne 0 ]; then + echo "An error occurred while downloading nuget.exe." + exit 1 + fi +fi + +# Restore tools from NuGet. +pushd "$TOOLS_DIR" >/dev/null +if [ ! -f "$PACKAGES_CONFIG_MD5" ] || [ "$( cat "$PACKAGES_CONFIG_MD5" | sed 's/\r$//' )" != "$( $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' )" ]; then + find . -type d ! -name . ! -name 'Cake.Bakery' | xargs rm -rf +fi + +mono "$NUGET_EXE" install -ExcludeVersion +if [ $? -ne 0 ]; then + echo "Could not restore NuGet tools." + exit 1 +fi + +$MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' >| "$PACKAGES_CONFIG_MD5" + +popd >/dev/null + +# Restore addins from NuGet. +if [ -f "$ADDINS_PACKAGES_CONFIG" ]; then + pushd "$ADDINS_DIR" >/dev/null + + mono "$NUGET_EXE" install -ExcludeVersion + if [ $? -ne 0 ]; then + echo "Could not restore NuGet addins." + exit 1 + fi + + popd >/dev/null +fi + +# Restore modules from NuGet. +if [ -f "$MODULES_PACKAGES_CONFIG" ]; then + pushd "$MODULES_DIR" >/dev/null + + mono "$NUGET_EXE" install -ExcludeVersion + if [ $? -ne 0 ]; then + echo "Could not restore NuGet modules." + exit 1 + fi + + popd >/dev/null +fi + +# Make sure that Cake has been installed. +if [ ! -f "$CAKE_EXE" ]; then + echo "Could not find Cake.exe at '$CAKE_EXE'." + exit 1 +fi + +# Start Cake +exec mono "$CAKE_EXE" $SCRIPT "${CAKE_ARGUMENTS[@]}" diff --git a/dotnet-install.ps1 b/dotnet-install.ps1 new file mode 100644 index 0000000..16e9be8 --- /dev/null +++ b/dotnet-install.ps1 @@ -0,0 +1,686 @@ +# +# Copyright (c) .NET Foundation and contributors. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. +# + +<# +.SYNOPSIS + Installs dotnet cli +.DESCRIPTION + Installs dotnet cli. If dotnet installation already exists in the given directory + it will update it only if the requested version differs from the one already installed. +.PARAMETER Channel + Default: LTS + Download from the Channel specified. Possible values: + - Current - most current release + - LTS - most current supported release + - 2-part version in a format A.B - represents a specific release + examples: 2.0, 1.0 + - Branch name + examples: release/2.0.0, Master + Note: The version parameter overrides the channel parameter. +.PARAMETER Version + Default: latest + Represents a build version on specific channel. Possible values: + - latest - most latest build on specific channel + - coherent - most latest coherent build on specific channel + coherent applies only to SDK downloads + - 3-part version in a format A.B.C - represents specific version of build + examples: 2.0.0-preview2-006120, 1.1.0 +.PARAMETER InstallDir + Default: %LocalAppData%\Microsoft\dotnet + Path to where to install dotnet. Note that binaries will be placed directly in a given directory. +.PARAMETER Architecture + Default: - this value represents currently running OS architecture + Architecture of dotnet binaries to be installed. + Possible values are: , amd64, x64, x86, arm64, arm +.PARAMETER SharedRuntime + This parameter is obsolete and may be removed in a future version of this script. + The recommended alternative is '-Runtime dotnet'. + Installs just the shared runtime bits, not the entire SDK. +.PARAMETER Runtime + Installs just a shared runtime, not the entire SDK. + Possible values: + - dotnet - the Microsoft.NETCore.App shared runtime + - aspnetcore - the Microsoft.AspNetCore.App shared runtime + - windowsdesktop - the Microsoft.WindowsDesktop.App shared runtime +.PARAMETER DryRun + If set it will not perform installation but instead display what command line to use to consistently install + currently requested version of dotnet cli. In example if you specify version 'latest' it will display a link + with specific version so that this command can be used deterministicly in a build script. + It also displays binaries location if you prefer to install or download it yourself. +.PARAMETER NoPath + By default this script will set environment variable PATH for the current process to the binaries folder inside installation folder. + If set it will display binaries location but not set any environment variable. +.PARAMETER Verbose + Displays diagnostics information. +.PARAMETER AzureFeed + Default: https://dotnetcli.azureedge.net/dotnet + This parameter typically is not changed by the user. + It allows changing the URL for the Azure feed used by this installer. +.PARAMETER UncachedFeed + This parameter typically is not changed by the user. + It allows changing the URL for the Uncached feed used by this installer. +.PARAMETER FeedCredential + Used as a query string to append to the Azure feed. + It allows changing the URL to use non-public blob storage accounts. +.PARAMETER ProxyAddress + If set, the installer will use the proxy when making web requests +.PARAMETER ProxyUseDefaultCredentials + Default: false + Use default credentials, when using proxy address. +.PARAMETER SkipNonVersionedFiles + Default: false + Skips installing non-versioned files if they already exist, such as dotnet.exe. +.PARAMETER NoCdn + Disable downloading from the Azure CDN, and use the uncached feed directly. +.PARAMETER JSonFile + Determines the SDK version from a user specified global.json file + Note: global.json must have a value for 'SDK:Version' +#> +[cmdletbinding()] +param( + [string]$Channel="LTS", + [string]$Version="Latest", + [string]$JSonFile, + [string]$InstallDir="", + [string]$Architecture="", + [ValidateSet("dotnet", "aspnetcore", "windowsdesktop", IgnoreCase = $false)] + [string]$Runtime, + [Obsolete("This parameter may be removed in a future version of this script. The recommended alternative is '-Runtime dotnet'.")] + [switch]$SharedRuntime, + [switch]$DryRun, + [switch]$NoPath, + [string]$AzureFeed="https://dotnetcli.azureedge.net/dotnet", + [string]$UncachedFeed="https://dotnetcli.blob.core.windows.net/dotnet", + [string]$FeedCredential, + [string]$ProxyAddress, + [switch]$ProxyUseDefaultCredentials, + [switch]$SkipNonVersionedFiles, + [switch]$NoCdn +) + +Set-StrictMode -Version Latest +$ErrorActionPreference="Stop" +$ProgressPreference="SilentlyContinue" + +if ($NoCdn) { + $AzureFeed = $UncachedFeed +} + +$BinFolderRelativePath="" + +if ($SharedRuntime -and (-not $Runtime)) { + $Runtime = "dotnet" +} + +# example path with regex: shared/1.0.0-beta-12345/somepath +$VersionRegEx="/\d+\.\d+[^/]+/" +$OverrideNonVersionedFiles = !$SkipNonVersionedFiles + +function Say($str) { + Write-Host "dotnet-install: $str" +} + +function Say-Verbose($str) { + Write-Verbose "dotnet-install: $str" +} + +function Say-Invocation($Invocation) { + $command = $Invocation.MyCommand; + $args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ") + Say-Verbose "$command $args" +} + +function Invoke-With-Retry([ScriptBlock]$ScriptBlock, [int]$MaxAttempts = 3, [int]$SecondsBetweenAttempts = 1) { + $Attempts = 0 + + while ($true) { + try { + return $ScriptBlock.Invoke() + } + catch { + $Attempts++ + if ($Attempts -lt $MaxAttempts) { + Start-Sleep $SecondsBetweenAttempts + } + else { + throw + } + } + } +} + +function Get-Machine-Architecture() { + Say-Invocation $MyInvocation + + # possible values: amd64, x64, x86, arm64, arm + return $ENV:PROCESSOR_ARCHITECTURE +} + +function Get-CLIArchitecture-From-Architecture([string]$Architecture) { + Say-Invocation $MyInvocation + + switch ($Architecture.ToLower()) { + { $_ -eq "" } { return Get-CLIArchitecture-From-Architecture $(Get-Machine-Architecture) } + { ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" } + { $_ -eq "x86" } { return "x86" } + { $_ -eq "arm" } { return "arm" } + { $_ -eq "arm64" } { return "arm64" } + default { throw "Architecture not supported. If you think this is a bug, report it at https://github.com/dotnet/sdk/issues" } + } +} + +# The version text returned from the feeds is a 1-line or 2-line string: +# For the SDK and the dotnet runtime (2 lines): +# Line 1: # commit_hash +# Line 2: # 4-part version +# For the aspnetcore runtime (1 line): +# Line 1: # 4-part version +function Get-Version-Info-From-Version-Text([string]$VersionText) { + Say-Invocation $MyInvocation + + $Data = -split $VersionText + + $VersionInfo = @{ + CommitHash = $(if ($Data.Count -gt 1) { $Data[0] }) + Version = $Data[-1] # last line is always the version number. + } + return $VersionInfo +} + +function Load-Assembly([string] $Assembly) { + try { + Add-Type -Assembly $Assembly | Out-Null + } + catch { + # On Nano Server, Powershell Core Edition is used. Add-Type is unable to resolve base class assemblies because they are not GAC'd. + # Loading the base class assemblies is not unnecessary as the types will automatically get resolved. + } +} + +function GetHTTPResponse([Uri] $Uri) +{ + Invoke-With-Retry( + { + + $HttpClient = $null + + try { + # HttpClient is used vs Invoke-WebRequest in order to support Nano Server which doesn't support the Invoke-WebRequest cmdlet. + Load-Assembly -Assembly System.Net.Http + + if(-not $ProxyAddress) { + try { + # Despite no proxy being explicitly specified, we may still be behind a default proxy + $DefaultProxy = [System.Net.WebRequest]::DefaultWebProxy; + if($DefaultProxy -and (-not $DefaultProxy.IsBypassed($Uri))) { + $ProxyAddress = $DefaultProxy.GetProxy($Uri).OriginalString + $ProxyUseDefaultCredentials = $true + } + } catch { + # Eat the exception and move forward as the above code is an attempt + # at resolving the DefaultProxy that may not have been a problem. + $ProxyAddress = $null + Say-Verbose("Exception ignored: $_.Exception.Message - moving forward...") + } + } + + if($ProxyAddress) { + $HttpClientHandler = New-Object System.Net.Http.HttpClientHandler + $HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{Address=$ProxyAddress;UseDefaultCredentials=$ProxyUseDefaultCredentials} + $HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler + } + else { + + $HttpClient = New-Object System.Net.Http.HttpClient + } + # Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out + # 20 minutes allows it to work over much slower connections. + $HttpClient.Timeout = New-TimeSpan -Minutes 20 + $Response = $HttpClient.GetAsync("${Uri}${FeedCredential}").Result + if (($Response -eq $null) -or (-not ($Response.IsSuccessStatusCode))) { + # The feed credential is potentially sensitive info. Do not log FeedCredential to console output. + $ErrorMsg = "Failed to download $Uri." + if ($Response -ne $null) { + $ErrorMsg += " $Response" + } + + throw $ErrorMsg + } + + return $Response + } + finally { + if ($HttpClient -ne $null) { + $HttpClient.Dispose() + } + } + }) +} + +function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Coherent) { + Say-Invocation $MyInvocation + + $VersionFileUrl = $null + if ($Runtime -eq "dotnet") { + $VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version" + } + elseif ($Runtime -eq "aspnetcore") { + $VersionFileUrl = "$UncachedFeed/aspnetcore/Runtime/$Channel/latest.version" + } + # Currently, the WindowsDesktop runtime is manufactured with the .Net core runtime + elseif ($Runtime -eq "windowsdesktop") { + $VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version" + } + elseif (-not $Runtime) { + if ($Coherent) { + $VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.coherent.version" + } + else { + $VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.version" + } + } + else { + throw "Invalid value for `$Runtime" + } + try { + $Response = GetHTTPResponse -Uri $VersionFileUrl + } + catch { + throw "Could not resolve version information." + } + $StringContent = $Response.Content.ReadAsStringAsync().Result + + switch ($Response.Content.Headers.ContentType) { + { ($_ -eq "application/octet-stream") } { $VersionText = $StringContent } + { ($_ -eq "text/plain") } { $VersionText = $StringContent } + { ($_ -eq "text/plain; charset=UTF-8") } { $VersionText = $StringContent } + default { throw "``$Response.Content.Headers.ContentType`` is an unknown .version file content type." } + } + + $VersionInfo = Get-Version-Info-From-Version-Text $VersionText + + return $VersionInfo +} + +function Parse-Jsonfile-For-Version([string]$JSonFile) { + Say-Invocation $MyInvocation + + If (-Not (Test-Path $JSonFile)) { + throw "Unable to find '$JSonFile'" + } + try { + $JSonContent = Get-Content($JSonFile) -Raw | ConvertFrom-Json | Select-Object -expand "sdk" -ErrorAction SilentlyContinue + } + catch { + throw "Json file unreadable: '$JSonFile'" + } + if ($JSonContent) { + try { + $JSonContent.PSObject.Properties | ForEach-Object { + $PropertyName = $_.Name + if ($PropertyName -eq "version") { + $Version = $_.Value + Say-Verbose "Version = $Version" + } + } + } + catch { + throw "Unable to parse the SDK node in '$JSonFile'" + } + } + else { + throw "Unable to find the SDK node in '$JSonFile'" + } + If ($Version -eq $null) { + throw "Unable to find the SDK:version node in '$JSonFile'" + } + return $Version +} + +function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, [string]$Version, [string]$JSonFile) { + Say-Invocation $MyInvocation + + if (-not $JSonFile) { + switch ($Version.ToLower()) { + { $_ -eq "latest" } { + $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $False + return $LatestVersionInfo.Version + } + { $_ -eq "coherent" } { + $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $True + return $LatestVersionInfo.Version + } + default { return $Version } + } + } + else { + return Parse-Jsonfile-For-Version $JSonFile + } +} + +function Get-Download-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { + Say-Invocation $MyInvocation + + if ($Runtime -eq "dotnet") { + $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificVersion-win-$CLIArchitecture.zip" + } + elseif ($Runtime -eq "aspnetcore") { + $PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificVersion-win-$CLIArchitecture.zip" + } + elseif ($Runtime -eq "windowsdesktop") { + $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/windowsdesktop-runtime-$SpecificVersion-win-$CLIArchitecture.zip" + } + elseif (-not $Runtime) { + $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificVersion-win-$CLIArchitecture.zip" + } + else { + throw "Invalid value for `$Runtime" + } + + Say-Verbose "Constructed primary named payload URL: $PayloadURL" + + return $PayloadURL +} + +function Get-LegacyDownload-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { + Say-Invocation $MyInvocation + + if (-not $Runtime) { + $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-dev-win-$CLIArchitecture.$SpecificVersion.zip" + } + elseif ($Runtime -eq "dotnet") { + $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-win-$CLIArchitecture.$SpecificVersion.zip" + } + else { + return $null + } + + Say-Verbose "Constructed legacy named payload URL: $PayloadURL" + + return $PayloadURL +} + +function Get-User-Share-Path() { + Say-Invocation $MyInvocation + + $InstallRoot = $env:DOTNET_INSTALL_DIR + if (!$InstallRoot) { + $InstallRoot = "$env:LocalAppData\Microsoft\dotnet" + } + return $InstallRoot +} + +function Resolve-Installation-Path([string]$InstallDir) { + Say-Invocation $MyInvocation + + if ($InstallDir -eq "") { + return Get-User-Share-Path + } + return $InstallDir +} + +function Is-Dotnet-Package-Installed([string]$InstallRoot, [string]$RelativePathToPackage, [string]$SpecificVersion) { + Say-Invocation $MyInvocation + + $DotnetPackagePath = Join-Path -Path $InstallRoot -ChildPath $RelativePathToPackage | Join-Path -ChildPath $SpecificVersion + Say-Verbose "Is-Dotnet-Package-Installed: DotnetPackagePath=$DotnetPackagePath" + return Test-Path $DotnetPackagePath -PathType Container +} + +function Get-Absolute-Path([string]$RelativeOrAbsolutePath) { + # Too much spam + # Say-Invocation $MyInvocation + + return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($RelativeOrAbsolutePath) +} + +function Get-Path-Prefix-With-Version($path) { + $match = [regex]::match($path, $VersionRegEx) + if ($match.Success) { + return $entry.FullName.Substring(0, $match.Index + $match.Length) + } + + return $null +} + +function Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package([System.IO.Compression.ZipArchive]$Zip, [string]$OutPath) { + Say-Invocation $MyInvocation + + $ret = @() + foreach ($entry in $Zip.Entries) { + $dir = Get-Path-Prefix-With-Version $entry.FullName + if ($dir -ne $null) { + $path = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $dir) + if (-Not (Test-Path $path -PathType Container)) { + $ret += $dir + } + } + } + + $ret = $ret | Sort-Object | Get-Unique + + $values = ($ret | foreach { "$_" }) -join ";" + Say-Verbose "Directories to unpack: $values" + + return $ret +} + +# Example zip content and extraction algorithm: +# Rule: files if extracted are always being extracted to the same relative path locally +# .\ +# a.exe # file does not exist locally, extract +# b.dll # file exists locally, override only if $OverrideFiles set +# aaa\ # same rules as for files +# ... +# abc\1.0.0\ # directory contains version and exists locally +# ... # do not extract content under versioned part +# abc\asd\ # same rules as for files +# ... +# def\ghi\1.0.1\ # directory contains version and does not exist locally +# ... # extract content +function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { + Say-Invocation $MyInvocation + + Load-Assembly -Assembly System.IO.Compression.FileSystem + Set-Variable -Name Zip + try { + $Zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath) + + $DirectoriesToUnpack = Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package -Zip $Zip -OutPath $OutPath + + foreach ($entry in $Zip.Entries) { + $PathWithVersion = Get-Path-Prefix-With-Version $entry.FullName + if (($PathWithVersion -eq $null) -Or ($DirectoriesToUnpack -contains $PathWithVersion)) { + $DestinationPath = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $entry.FullName) + $DestinationDir = Split-Path -Parent $DestinationPath + $OverrideFiles=$OverrideNonVersionedFiles -Or (-Not (Test-Path $DestinationPath)) + if ((-Not $DestinationPath.EndsWith("\")) -And $OverrideFiles) { + New-Item -ItemType Directory -Force -Path $DestinationDir | Out-Null + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $DestinationPath, $OverrideNonVersionedFiles) + } + } + } + } + finally { + if ($Zip -ne $null) { + $Zip.Dispose() + } + } +} + +function DownloadFile($Source, [string]$OutPath) { + if ($Source -notlike "http*") { + # Using System.IO.Path.GetFullPath to get the current directory + # does not work in this context - $pwd gives the current directory + if (![System.IO.Path]::IsPathRooted($Source)) { + $Source = $(Join-Path -Path $pwd -ChildPath $Source) + } + $Source = Get-Absolute-Path $Source + Say "Copying file from $Source to $OutPath" + Copy-Item $Source $OutPath + return + } + + $Stream = $null + + try { + $Response = GetHTTPResponse -Uri $Source + $Stream = $Response.Content.ReadAsStreamAsync().Result + $File = [System.IO.File]::Create($OutPath) + $Stream.CopyTo($File) + $File.Close() + } + finally { + if ($Stream -ne $null) { + $Stream.Dispose() + } + } +} + +function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolderRelativePath) { + $BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath $BinFolderRelativePath) + if (-Not $NoPath) { + $SuffixedBinPath = "$BinPath;" + if (-Not $env:path.Contains($SuffixedBinPath)) { + Say "Adding to current process PATH: `"$BinPath`". Note: This change will not be visible if PowerShell was run as a child process." + $env:path = $SuffixedBinPath + $env:path + } else { + Say-Verbose "Current process PATH already contains `"$BinPath`"" + } + } + else { + Say "Binaries of dotnet can be found in $BinPath" + } +} + +$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture +$SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version -JSonFile $JSonFile +$DownloadLink = Get-Download-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture +$LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture + +$InstallRoot = Resolve-Installation-Path $InstallDir +Say-Verbose "InstallRoot: $InstallRoot" +$ScriptName = $MyInvocation.MyCommand.Name + +if ($DryRun) { + Say "Payload URLs:" + Say "Primary named payload URL: $DownloadLink" + if ($LegacyDownloadLink) { + Say "Legacy named payload URL: $LegacyDownloadLink" + } + $RepeatableCommand = ".\$ScriptName -Version `"$SpecificVersion`" -InstallDir `"$InstallRoot`" -Architecture `"$CLIArchitecture`"" + if ($Runtime -eq "dotnet") { + $RepeatableCommand+=" -Runtime `"dotnet`"" + } + elseif ($Runtime -eq "aspnetcore") { + $RepeatableCommand+=" -Runtime `"aspnetcore`"" + } + foreach ($key in $MyInvocation.BoundParameters.Keys) { + if (-not (@("Architecture","Channel","DryRun","InstallDir","Runtime","SharedRuntime","Version") -contains $key)) { + $RepeatableCommand+=" -$key `"$($MyInvocation.BoundParameters[$key])`"" + } + } + Say "Repeatable invocation: $RepeatableCommand" + exit 0 +} + +if ($Runtime -eq "dotnet") { + $assetName = ".NET Core Runtime" + $dotnetPackageRelativePath = "shared\Microsoft.NETCore.App" +} +elseif ($Runtime -eq "aspnetcore") { + $assetName = "ASP.NET Core Runtime" + $dotnetPackageRelativePath = "shared\Microsoft.AspNetCore.App" +} +elseif ($Runtime -eq "windowsdesktop") { + $assetName = ".NET Core Windows Desktop Runtime" + $dotnetPackageRelativePath = "shared\Microsoft.WindowsDesktop.App" +} +elseif (-not $Runtime) { + $assetName = ".NET Core SDK" + $dotnetPackageRelativePath = "sdk" +} +else { + throw "Invalid value for `$Runtime" +} + +# Check if the SDK version is already installed. +$isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion +if ($isAssetInstalled) { + Say "$assetName version $SpecificVersion is already installed." + Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath + exit 0 +} + +New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null + +$installDrive = $((Get-Item $InstallRoot).PSDrive.Name); +$diskInfo = Get-PSDrive -Name $installDrive +if ($diskInfo.Free / 1MB -le 100) { + Say "There is not enough disk space on drive ${installDrive}:" + exit 0 +} + +$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) +Say-Verbose "Zip path: $ZipPath" + +$DownloadFailed = $false +Say "Downloading link: $DownloadLink" +try { + DownloadFile -Source $DownloadLink -OutPath $ZipPath +} +catch { + Say "Cannot download: $DownloadLink" + if ($LegacyDownloadLink) { + $DownloadLink = $LegacyDownloadLink + $ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) + Say-Verbose "Legacy zip path: $ZipPath" + Say "Downloading legacy link: $DownloadLink" + try { + DownloadFile -Source $DownloadLink -OutPath $ZipPath + } + catch { + Say "Cannot download: $DownloadLink" + $DownloadFailed = $true + } + } + else { + $DownloadFailed = $true + } +} + +if ($DownloadFailed) { + throw "Could not find/download: `"$assetName`" with version = $SpecificVersion`nRefer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support" +} + +Say "Extracting zip from $DownloadLink" +Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot + +# Check if the SDK version is installed; if not, fail the installation. +$isAssetInstalled = $false + +# if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed. +if ($SpecificVersion -Match "rtm" -or $SpecificVersion -Match "servicing") { + $ReleaseVersion = $SpecificVersion.Split("-")[0] + Say-Verbose "Checking installation: version = $ReleaseVersion" + $isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $ReleaseVersion +} + +# Check if the SDK version is installed. +if (!$isAssetInstalled) { + Say-Verbose "Checking installation: version = $SpecificVersion" + $isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion +} + +if (!$isAssetInstalled) { + throw "`"$assetName`" with version = $SpecificVersion failed to install with an unknown error." +} + +Remove-Item $ZipPath + +Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath + +Say "Installation finished" +exit 0 diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..a11058c --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,45 @@ + + + + netcoreapp3.1 + True + false + true + latest + True + + + + pdbonly + + + + portable + + + + shatl + Alphacloud + (c) Alphacloud + MIT + https://github.com/alphacloud/dotnet-readbin + https://github.com/alphacloud/dotnet-readbin/releases/tag/1.0.0 + dotnet-cli bson messagepack alphacloud + git + https://github.com/alphacloud/dotnet-readbin + dotnet-readbin + + + + true + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + $(NoWarn);0618;1591 + true + False + + + diff --git a/src/common/README.md b/src/common/README.md new file mode 100644 index 0000000..e610b9f --- /dev/null +++ b/src/common/README.md @@ -0,0 +1,2 @@ +Common files: +* AssemblyVersion.cs is auto-generated and should not be stored in VCS. diff --git a/src/dotnet-readbin.sln b/src/dotnet-readbin.sln new file mode 100644 index 0000000..c0fba68 --- /dev/null +++ b/src/dotnet-readbin.sln @@ -0,0 +1,68 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.352 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-readbin", "dotnet-readbin\dotnet-readbin.csproj", "{D09DABA7-905E-49BA-9AFC-CD9A9ADED706}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".build", ".build", "{94B7625C-D9B6-4F9C-911E-6FB8A38FC889}" + ProjectSection(SolutionItems) = preProject + ..\appveyor.yml = ..\appveyor.yml + ..\build.cake = ..\build.cake + ..\CONTRIBUTING.md = ..\CONTRIBUTING.md + ..\GitReleaseManager.yaml = ..\GitReleaseManager.yaml + ..\GitVersion.yml = ..\GitVersion.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{5081DE28-F1F2-422A-B5C0-7AA3B104483A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadBin.Tests", "tests\ReadBin.Tests\ReadBin.Tests.csproj", "{83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "app", "app", "{80950E40-D426-4D35-97A6-36380AEC8484}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706}.Debug|x64.ActiveCfg = Debug|Any CPU + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706}.Debug|x64.Build.0 = Debug|Any CPU + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706}.Debug|x86.ActiveCfg = Debug|Any CPU + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706}.Debug|x86.Build.0 = Debug|Any CPU + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706}.Release|Any CPU.Build.0 = Release|Any CPU + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706}.Release|x64.ActiveCfg = Release|Any CPU + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706}.Release|x64.Build.0 = Release|Any CPU + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706}.Release|x86.ActiveCfg = Release|Any CPU + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706}.Release|x86.Build.0 = Release|Any CPU + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}.Debug|x64.ActiveCfg = Debug|Any CPU + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}.Debug|x64.Build.0 = Debug|Any CPU + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}.Debug|x86.ActiveCfg = Debug|Any CPU + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}.Debug|x86.Build.0 = Debug|Any CPU + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}.Release|Any CPU.Build.0 = Release|Any CPU + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}.Release|x64.ActiveCfg = Release|Any CPU + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}.Release|x64.Build.0 = Release|Any CPU + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}.Release|x86.ActiveCfg = Release|Any CPU + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D09DABA7-905E-49BA-9AFC-CD9A9ADED706} = {80950E40-D426-4D35-97A6-36380AEC8484} + {83AE0AF3-1B51-41C5-A081-9BB6B8D8A765} = {5081DE28-F1F2-422A-B5C0-7AA3B104483A} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6BB7BDF5-FAD6-46BA-91D9-8735A12485B3} + EndGlobalSection +EndGlobal diff --git a/src/dotnet-readbin.sln.DotSettings b/src/dotnet-readbin.sln.DotSettings new file mode 100644 index 0000000..d1914d4 --- /dev/null +++ b/src/dotnet-readbin.sln.DotSettings @@ -0,0 +1,398 @@ + + True + True + True + False + False + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + Inherit + True + // Use the following placeholders: +// $EXPR$ -- source expression +// $NAME$ -- source name (string literal or 'nameof' expression) +// $MESSAGE$ -- string literal in the form of "$NAME$ != null" +FluentAssertions.AssertionExtensions.Should($EXPR$).NotBeNull() + + True + True + LibLog.cs + SOLUTION + True + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + True + True + <?xml version="1.0" encoding="utf-16"?><Profile name="SharpArch"><CSReorderTypeMembers>True</CSReorderTypeMembers><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="False" /><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><CssAlphabetizeProperties>True</CssAlphabetizeProperties><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><VBReformatCode>True</VBReformatCode><VBFormatDocComments>True</VBFormatDocComments><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments></Profile> + SharpArch + True + RequiredForMultiline + RequiredForMultilineStatement + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + ExpressionBody + False + True + 2 + True + True + True + True + 1 + 1 + False + True + NEVER + False + True + 150 + CHOP_ALWAYS + 1 + 100 + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="Non-reorderable types"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + <HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" /> + <HasAttribute Name="JetBrains.Annotations.NoReorder" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="xUnit.net Test Classes"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasMember> + <And> + <Kind Is="Method" /> + <HasAttribute Name="Xunit.FactAttribute" Inherited="True" /> + </And> + </HasMember> + </And> + </TypePattern.Match> + <Entry DisplayName="Static Fields and Constants"> + <Entry.Match> + <Or> + <Kind Is="Constant" /> + <And> + <Kind Is="Field" /> + <Static /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Kind Order="Constant Field" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Fields"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Not> + <Static /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Readonly /> + <ImplementsInterface /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Public Delegates" Priority="100"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Delegate" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Public Enums" Priority="100"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Enum" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <Or> + <Kind Is="Constructor" /> + <And> + <Kind Is="Method" /> + <ImplementsInterface Name="System.IDisposable" /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Kind Order="Constructor" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Test Methods" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="Xunit.FactAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Test Methods" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Entry DisplayName="Static Fields and Constants"> + <Entry.Match> + <Or> + <Kind Is="Constant" /> + <And> + <Kind Is="Field" /> + <Static /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Kind Order="Constant Field" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Fields"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Not> + <Static /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Readonly /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Public Delegates" Priority="100"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Delegate" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Public Enums" Priority="100"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Enum" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Properties, Indexers"> + <Entry.Match> + <Or> + <Kind Is="Property" /> + <Kind Is="Indexer" /> + </Or> + </Entry.Match> + </Entry> + <Entry DisplayName="Constructors"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + <Entry.SortBy> + <Static /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Interface Implementations" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Member" /> + <ImplementsInterface /> + </And> + </Entry.Match> + <Entry.SortBy> + <ImplementsInterface Immediate="True" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Nested Types"> + <Entry.Match> + <Kind Is="Type" /> + </Entry.Match> + </Entry> + </TypePattern> +</Patterns> + True + True + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + OUTLINE + True + 2 + True + LIVE_MONITOR + DO_NOTHING + DO_NOTHING + DO_NOTHING + DO_NOTHING + DO_NOTHING + LIVE_MONITOR + LIVE_MONITOR + DO_NOTHING + DO_NOTHING + LIVE_MONITOR + DO_NOTHING + DO_NOTHING + + True + + True + + True + + True + + True + + True + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + IfRequired + ALWAYS + True + True + True + True + True + True \ No newline at end of file diff --git a/src/dotnet-readbin/Commands/BaseReadCommand.cs b/src/dotnet-readbin/Commands/BaseReadCommand.cs new file mode 100644 index 0000000..47920f4 --- /dev/null +++ b/src/dotnet-readbin/Commands/BaseReadCommand.cs @@ -0,0 +1,74 @@ +namespace Alphacloud.DotNet.ReadBin.Commands +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using JetBrains.Annotations; + using McMaster.Extensions.CommandLineUtils; + + + /// + /// Base class for read commands. + /// + [HelpOption("--help")] + public abstract class BaseReadCommand + { + /// + /// Input file name. + /// + [Option(ShortName = "in", LongName = "input-file", + Description = "Input file name (console, if not specified).")] + [FileExists] + public string InputFileName { get; set; } + + /// + /// Output file name. + /// + [Option(ShortName = "out", LongName = "output-file", + Description = "Input file name (console, if not specified).")] + public string OutputFileName { get; set; } + + /// + /// Returns stream for input data. + /// + /// + protected Stream GetInputStream() + { + return string.IsNullOrEmpty(InputFileName) + ? Console.OpenStandardInput() + : File.OpenRead(InputFileName); + } + + /// + /// Returns stream to output data. + /// + /// + protected Stream GetOutputStream() + { + return string.IsNullOrEmpty(OutputFileName) + ? Console.OpenStandardOutput(100) + : new FileStream(OutputFileName, FileMode.Create); + } + + /// + /// Executes transformation. + /// + /// Exit code to return to calling process, . + [UsedImplicitly] + public virtual async Task OnExecute(CommandLineApplication app, IConsole console) + { + var input = GetInputStream(); + await using (input.ConfigureAwait(false)) + { + var output = GetOutputStream(); + await using (output.ConfigureAwait(false)) + { + return await Transform(input, output, CancellationToken.None).ConfigureAwait(false); + } + } + } + + internal abstract Task Transform(Stream input, Stream output, CancellationToken cancellationToken); + } +} diff --git a/src/dotnet-readbin/Commands/Encodings.cs b/src/dotnet-readbin/Commands/Encodings.cs new file mode 100644 index 0000000..0323292 --- /dev/null +++ b/src/dotnet-readbin/Commands/Encodings.cs @@ -0,0 +1,16 @@ +namespace Alphacloud.DotNet.ReadBin.Commands +{ + using System.Text; + + + /// + /// Text encodings supported by application. + /// + internal static class Encodings + { + /// + /// UTF-8 without byte order marker. + /// + public static readonly Encoding Utf8NoBom = new UTF8Encoding(false); + } +} diff --git a/src/dotnet-readbin/Commands/ReadBase64Command.cs b/src/dotnet-readbin/Commands/ReadBase64Command.cs new file mode 100644 index 0000000..19ac3b1 --- /dev/null +++ b/src/dotnet-readbin/Commands/ReadBase64Command.cs @@ -0,0 +1,46 @@ +namespace Alphacloud.DotNet.ReadBin.Commands +{ + using System; + using System.IO; + using System.Security.Cryptography; + using System.Threading; + using System.Threading.Tasks; + using McMaster.Extensions.CommandLineUtils; + + + [Command("base64", Description = "Convert Base64 encoded data to binary.")] + public class ReadBase64Command : BaseReadCommand + { + /// + internal override async Task Transform(Stream input, Stream output, CancellationToken cancellationToken) + { + const int bufferSize = 512; + var inputBuffer = new byte[bufferSize]; + var outputBuffer = new byte[bufferSize]; + using (var transform = new FromBase64Transform()) + { + int bytesRead; + while ((bytesRead = await input.ReadAsync(inputBuffer, 0, inputBuffer.Length, cancellationToken).ConfigureAwait(false)) > 0) + { + int bytesTransformed; + try + { + bytesTransformed = transform.TransformBlock(inputBuffer, 0, bytesRead, outputBuffer, 0); + } + catch (Exception ex) + { + var offset = input.Position - bufferSize; + throw new InvalidOperationException( + $"Error decoding input block of {bufferSize} bytes starting at offset {offset} ({offset:x2}).", + ex); + } + + await output.WriteAsync(outputBuffer, 0, bytesTransformed, cancellationToken).ConfigureAwait(false); + } + } + + return ExitCodes.Ok; + } + } +} + diff --git a/src/dotnet-readbin/Commands/ReadBsonCommand.cs b/src/dotnet-readbin/Commands/ReadBsonCommand.cs new file mode 100644 index 0000000..8c7fdf1 --- /dev/null +++ b/src/dotnet-readbin/Commands/ReadBsonCommand.cs @@ -0,0 +1,54 @@ +namespace Alphacloud.DotNet.ReadBin.Commands +{ + using System; + using System.IO; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using McMaster.Extensions.CommandLineUtils; + using Newtonsoft.Json; + using Newtonsoft.Json.Bson; + using Newtonsoft.Json.Linq; + + + [Command("bson", Description = "Dump BSON as JSON.")] + internal class ReadBsonCommand : BaseReadCommand + { + [Option(ShortName = "arr", LongName = "array", Description = "Read object as an array")] + public bool IsArray { get; set; } + + [Option(ShortName = "dt", LongName = "date-time", + Description = "DateTime kind to use when reading dates (Local,Utc).")] + public DateTimeKind DateTimeKindHandling { get; set; } = DateTimeKind.Local; + + [Option(ShortName = "b", LongName = "bom", + Description = "Add UTF8 identifier to output")] + public bool AddBom { get; set; } + + [Option(ShortName = "indent", LongName = "indent-output", + Description = "Ident output")] + public bool IndentOutput { get; set; } + + internal override async Task Transform(Stream input, Stream output, CancellationToken cancellationToken) + { + JObject obj; + using (var reader = new BsonDataReader(input, IsArray, DateTimeKindHandling)) + { + obj = await JObject.LoadAsync(reader, cancellationToken) + .ConfigureAwait(false); + } + + var enc = AddBom ? Encoding.UTF8 : Encodings.Utf8NoBom; + var writer = new JsonTextWriter(new StreamWriter(output, enc, 1024, true)); + if (IndentOutput) + writer.Formatting = Formatting.Indented; + + using (writer) + { + await obj.WriteToAsync(writer, cancellationToken).ConfigureAwait(false); + } + + return ExitCodes.Ok; + } + } +} diff --git a/src/dotnet-readbin/Commands/ReadHexCommand.cs b/src/dotnet-readbin/Commands/ReadHexCommand.cs new file mode 100644 index 0000000..63b68ee --- /dev/null +++ b/src/dotnet-readbin/Commands/ReadHexCommand.cs @@ -0,0 +1,83 @@ +namespace Alphacloud.DotNet.ReadBin.Commands +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using McMaster.Extensions.CommandLineUtils; + + + /// + /// Transform hex encoded data. + /// + [Command("hex", Description = "Convert hex encoded text to binary.", + ExtendedHelpText = "Following characters are considered as white-spaces and skipped: Tab, Space, '-' and new line." + )] + public class ReadHexCommand : BaseReadCommand + { + private static readonly char[] WhiteSpaces = new[] + { + ' ', '-', '\x0D', '\x0A', '\x09' + }.OrderBy(x => x).ToArray(); + + private static readonly IReadOnlyDictionary Hex = new Dictionary(16) + { + ['0'] = 0, + ['1'] = 1, + ['2'] = 2, + ['3'] = 3, + ['4'] = 4, + ['5'] = 5, + ['6'] = 6, + ['7'] = 7, + ['8'] = 8, + ['9'] = 9, + ['a'] = 10, + ['b'] = 11, + ['c'] = 12, + ['d'] = 13, + ['e'] = 14, + ['f'] = 15 + }; + + /// + /// Unexpected character in input + /// An I/O error occurs. + internal override async Task Transform(Stream input, Stream output, CancellationToken cancellationToken) + { + var buf = new byte[2]; + int charIndex = 0; + int offset = 0; + + var outStream = new BinaryWriter(output, Encoding.ASCII, true); + await using (outStream.ConfigureAwait(false)) + { + int nextByte = 0; + while ((nextByte = input.ReadByte()) != -1) + { + var c = char.ToLowerInvariant((char) (nextByte & 0xFF)); + offset++; + if (Array.BinarySearch(WhiteSpaces, c) >= 0) continue; + + if (!Hex.TryGetValue(c, out var hexValue)) + throw new InvalidOperationException($"Incorrect hex character '{c}' (0x{(byte) c:X}) at offset {offset:x2}."); + + buf[charIndex++] = hexValue; + if (charIndex == 2) + { + charIndex = 0; + byte b = (byte) ((byte) (buf[0] << 4) + buf[1]); + outStream.Write(b); + } + } + + if (charIndex != 0) throw new InvalidOperationException($"Unexpected end of input at offset {offset} (0x{offset:x2})."); + } + + return ExitCodes.Ok; + } + } +} diff --git a/src/dotnet-readbin/Commands/ReadMessagePackCommand.cs b/src/dotnet-readbin/Commands/ReadMessagePackCommand.cs new file mode 100644 index 0000000..e99db1d --- /dev/null +++ b/src/dotnet-readbin/Commands/ReadMessagePackCommand.cs @@ -0,0 +1,32 @@ +namespace Alphacloud.DotNet.ReadBin.Commands +{ + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using McMaster.Extensions.CommandLineUtils; + using MessagePack; + + + [Command("msgpack", Description = "Dump MessagePack as JSON.")] + internal class ReadMessagePackCommand : BaseReadCommand + { + /// + internal override async Task Transform(Stream input, Stream output, CancellationToken cancellationToken) + { + string json; + using (var ms = new MemoryStream()) + { + await input.CopyToAsync(ms, 1024, cancellationToken).ConfigureAwait(false); + json = MessagePackSerializer.ConvertToJson(ms.ToArray()); + } + + var writer = new StreamWriter(output, Encodings.Utf8NoBom, 512, true); + await using (writer.ConfigureAwait(false)) + { + await writer.WriteAsync(json).ConfigureAwait(false); + } + + return ExitCodes.Ok; + } + } +} diff --git a/src/dotnet-readbin/ExitCodes.cs b/src/dotnet-readbin/ExitCodes.cs new file mode 100644 index 0000000..57f2eca --- /dev/null +++ b/src/dotnet-readbin/ExitCodes.cs @@ -0,0 +1,9 @@ +namespace Alphacloud.DotNet.ReadBin +{ + public static class ExitCodes + { + public const int Exception = 2; + public const int Error = 1; + public const int Ok = 0; + } +} diff --git a/src/dotnet-readbin/Program.cs b/src/dotnet-readbin/Program.cs new file mode 100644 index 0000000..003d7a4 --- /dev/null +++ b/src/dotnet-readbin/Program.cs @@ -0,0 +1,30 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ReadBin.Tests")] + +namespace Alphacloud.DotNet.ReadBin +{ + using System; + using System.Threading.Tasks; + using McMaster.Extensions.CommandLineUtils; + + + internal class Program + { + public static async Task Main(string[] args) + { + try + { + return await CommandLineApplication.ExecuteAsync(args) + .ConfigureAwait(false); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine("Unexpected error: " + ex); + Console.ResetColor(); + return ExitCodes.Exception; + } + } + } +} diff --git a/src/dotnet-readbin/ReadBinCommand.cs b/src/dotnet-readbin/ReadBinCommand.cs new file mode 100644 index 0000000..dff2734 --- /dev/null +++ b/src/dotnet-readbin/ReadBinCommand.cs @@ -0,0 +1,32 @@ +namespace Alphacloud.DotNet.ReadBin +{ + using System.Threading.Tasks; + using Commands; + using JetBrains.Annotations; + using McMaster.Extensions.CommandLineUtils; + + + /// + /// Main command. + /// + [Command( + Name = "dotnet readbin", + FullName = "dotnet-readbin", + Description = "Displays binary encoded data in human readable format.", + ExtendedHelpText = "Application reads console input")] + [Subcommand( + typeof(ReadBsonCommand), typeof(ReadMessagePackCommand), typeof(ReadHexCommand), + typeof(ReadBase64Command) + )] + [HelpOption] + public class ReadBinCommand + { + [UsedImplicitly] + public Task OnExecute(CommandLineApplication app, IConsole console) + { + if (app.Arguments.Count == 0) app.ShowHelp(); + + return Task.FromResult(ExitCodes.Error); + } + } +} diff --git a/src/dotnet-readbin/dotnet-readbin.csproj b/src/dotnet-readbin/dotnet-readbin.csproj new file mode 100644 index 0000000..ff5fa90 --- /dev/null +++ b/src/dotnet-readbin/dotnet-readbin.csproj @@ -0,0 +1,33 @@ + + + + Exe + true + true + latest + dotnet-readbin + A simple tool for displaying human-readable representation of binary serialized data. + Alphacloud.DotNet.ReadBin + false + true + + + + dotnet-readbin + dotnet-cli bson messagepack alphacloud + dotnet-readbin + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tests/ReadBin.Tests/Base64Tests.cs b/src/tests/ReadBin.Tests/Base64Tests.cs new file mode 100644 index 0000000..3f6cc4d --- /dev/null +++ b/src/tests/ReadBin.Tests/Base64Tests.cs @@ -0,0 +1,49 @@ +namespace Alphacloud.DotNet.ReadBin.Tests +{ + using System; + using System.Text; + using System.Threading.Tasks; + using Commands; + using FluentAssertions; + using Xunit; + + + public class Base64Tests : TextBasedConverterTestsBase + { + [Fact] + public async Task CanDecode() + { + var base64String = Convert.ToBase64String(new byte[] {1, 2, 0xF3, 0xFF}, Base64FormattingOptions.None); + + SetInputStream(base64String, Encoding.ASCII); + await Run(); + + var result = Output.ToArray(); + result.Should().HaveCount(4); + result[0].Should().Be(1); + result[1].Should().Be(2); + result[2].Should().Be(0xF3); + result[3].Should().Be(0xFF); + } + + [Fact] + public async Task CanProcessLineBreaks() + { + var arr = new byte[128]; + for (int i = 0; i < 128; i++) + { + arr[i] = (byte) i; + } + + SetInputStream(Convert.ToBase64String(arr, Base64FormattingOptions.InsertLineBreaks), Encoding.ASCII); + await Run(); + + var result = Output.ToArray(); + result.Should().HaveCount(128); + for (int i = 0; i < 128; i++) + { + result[i].Should().Be((byte) i, "error at index {0}", i); + } + } + } +} diff --git a/src/tests/ReadBin.Tests/BsonTests.cs b/src/tests/ReadBin.Tests/BsonTests.cs new file mode 100644 index 0000000..371637a --- /dev/null +++ b/src/tests/ReadBin.Tests/BsonTests.cs @@ -0,0 +1,52 @@ +namespace Alphacloud.DotNet.ReadBin.Tests +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using Commands; + using FluentAssertions; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using Xunit; + + + public class BsonTests : IDisposable + { + private Stream _input; + private readonly MemoryStream _output; + private readonly JsonTextReader _outputReader; + private readonly ReadBsonCommand _command; + + public BsonTests() + { + _output = new MemoryStream(1024); + _outputReader = new JsonTextReader(new StreamReader(_output)); + + _command = new ReadBsonCommand(); + } + + public void Dispose() + { + _input?.Dispose(); + _output?.Dispose(); + _outputReader?.Close(); + } + + [Fact] + public async Task CanDump() + { + _input = GetType().Assembly.GetManifestResourceStream("Alphacloud.DotNet.ReadBin.Tests.Resources.SimplePayload.bson"); + var res = await _command.Transform(_input, _output, CancellationToken.None).ConfigureAwait(false); + res.Should().Be(0); + + _output.Position = 0; + + var dump = JObject.Load(_outputReader); + + dump["string"].Value().Should().Be("string"); + dump["int"].Value().Should().Be(100); + dump["boolean"].Value().Should().BeTrue(); + } + } +} diff --git a/src/tests/ReadBin.Tests/HexTests.cs b/src/tests/ReadBin.Tests/HexTests.cs new file mode 100644 index 0000000..e5a7914 --- /dev/null +++ b/src/tests/ReadBin.Tests/HexTests.cs @@ -0,0 +1,36 @@ +namespace Alphacloud.DotNet.ReadBin.Tests +{ + using System; + using System.Text; + using System.Threading.Tasks; + using Commands; + using FluentAssertions; + using Xunit; + + + public class HexTests : TextBasedConverterTestsBase + { + [Fact] + public async Task CanConvertHex() + { + SetInputStream("01 02 F3 FF" + Environment.NewLine, Encoding.ASCII); + await Run(); + + var result = Output.ToArray(); + result.Should().HaveCount(4); + result[0].Should().Be(1); + result[1].Should().Be(2); + result[2].Should().Be(0xF3); + result[3].Should().Be(0xFF); + } + + [Fact] + public async Task Should_Fail_On_Incomplete_Data() + { + SetInputStream("F3 F", Encoding.ASCII); + var error = (await this.Awaiting(x => x.Run()) + .Should().ThrowAsync()).Which; + error.Message.Should().Be("Unexpected end of input at offset 4 (0x04)."); + } + } +} diff --git a/src/tests/ReadBin.Tests/MsgPackTests.cs b/src/tests/ReadBin.Tests/MsgPackTests.cs new file mode 100644 index 0000000..2688e60 --- /dev/null +++ b/src/tests/ReadBin.Tests/MsgPackTests.cs @@ -0,0 +1,7 @@ +namespace Alphacloud.DotNet.ReadBin.Tests +{ + public class MsgPackTests + { + + } +} diff --git a/src/tests/ReadBin.Tests/ReadBin.Tests.csproj b/src/tests/ReadBin.Tests/ReadBin.Tests.csproj new file mode 100644 index 0000000..9d00284 --- /dev/null +++ b/src/tests/ReadBin.Tests/ReadBin.Tests.csproj @@ -0,0 +1,32 @@ + + + + Alphacloud.DotNet.ReadBin.Tests + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/src/tests/ReadBin.Tests/Resources/SimplePayload.bson b/src/tests/ReadBin.Tests/Resources/SimplePayload.bson new file mode 100644 index 0000000..623f144 Binary files /dev/null and b/src/tests/ReadBin.Tests/Resources/SimplePayload.bson differ diff --git a/src/tests/ReadBin.Tests/Resources/SinglePayload.msgpack b/src/tests/ReadBin.Tests/Resources/SinglePayload.msgpack new file mode 100644 index 0000000..043a883 --- /dev/null +++ b/src/tests/ReadBin.Tests/Resources/SinglePayload.msgpack @@ -0,0 +1 @@ +‚¢Id¥Value¨Value: 1 \ No newline at end of file diff --git a/src/tests/ReadBin.Tests/TextBasedConverterTestsBase.cs b/src/tests/ReadBin.Tests/TextBasedConverterTestsBase.cs new file mode 100644 index 0000000..1288de0 --- /dev/null +++ b/src/tests/ReadBin.Tests/TextBasedConverterTestsBase.cs @@ -0,0 +1,51 @@ +namespace Alphacloud.DotNet.ReadBin.Tests +{ + using System; + using System.IO; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Commands; + + + public class TextBasedConverterTestsBase : IDisposable + where TCommand: BaseReadCommand, new() + { + protected MemoryStream Input { get; } + protected MemoryStream Output { get; } + protected TCommand Command { get; } + + public TextBasedConverterTestsBase() + { + Input = new MemoryStream(); + Output = new MemoryStream(); + Command = new TCommand(); + } + + /// + public void Dispose() + { + Input?.Dispose(); + Output?.Dispose(); + } + + protected void SetInputStream(string str, Encoding encoding) + { + Input.Position = 0; + Input.SetLength(0); + using (var streamWriter = new StreamWriter(Input, encoding, leaveOpen: true)) + { + streamWriter.Write(str); + streamWriter.Flush(); + } + Input.Position = 0; + } + + protected async Task Run() + { + await Command.Transform(Input, Output, CancellationToken.None); + Output.SetLength(Output.Position); + Output.Position = 0; + } + } +}