NuGet for Source Using Add As Link (Part 1)
我有一个名为 SimpleArgs 的 GitHub 项目。该项目简化了 C# 项目中的命令行参数处理。然而,其中一个要求是提供一个选项,可以选择使用 SimpleArgs dll 或生成一个单一文件的可执行文件。是的,所有内容都在一个单一的 exe 中,因此引用 dll 不是一个选项。
我有一个名为 SimpleArgs 的 GitHub 项目。该项目简化了 C# 项目中的命令行参数处理。然而,其中一个要求是提供一个选项,可以选择使用 SimpleArgs dll 或生成一个单一文件的可执行文件。是的,所有内容都在一个单一的 exe 中,因此引用 dll 不是一个选项。
所以我从这个项目中创建了两个独立的 NuGet 包
- SimpleArgs – 这个 NuGet 包使用 dll
- SimpleArgs.Sources – 这个 NuGet 包添加源代码
我最常使用 SimpleArgs.Sources。我很快意识到,NuGet for source 的方式并不具有可扩展性。我有一个包含四个不同项目的解决方案,每个项目都是一个单一文件的可执行文件。结果是 SimpleArgs 的代码有许多副本。
MySolution /Packages <-- Copy of SimpleArgs source /SingleExe1 <-- Copy of SimpleArgs source /SingleExe2 <-- Copy of SimpleArgs source /SingleExe3 <-- Copy of SimpleArgs source /SingleExe4 <-- Copy of SimpleArgs source
这就是 5 份 SimpleArgs 源代码的副本。起初,这似乎不是一个大问题,事实上,它看起来只是一点小麻烦。我做的第一个更改之一是,将重复的源代码副本排除在 Git 之外。这有所帮助,但还不够。仍然存在多份源代码导致的问题。例如,我遇到了 SimpleArgs 的一个 bug。我修复了它,然后在一段时间后,我在同一解决方案中的另一个项目中遇到了相同的 bug。哦,是的。我只修复了一个 SimpleArgs 源代码副本中的 bug。
我决定最好的解决方案是使用“添加为链接”来引用源代码。“添加为链接”是指将一个文件包含到你的 Visual Studio 项目中,但不在你的项目中创建该文件的副本。
我很快就更改了项目,以便源代码不是以副本的形式包含,而是使用“添加为链接”功能。我是手动完成的。然后,我最终将我的更改推送到 SimpleArgs Git 存储库,并发布了新版本的 SimpleArgs.Sources NuGet 包。这基本上消除了我手动进行“添加为链接”的工作。
我需要 NuGet 包能为我使用“添加为链接”的方式包含源代码。
如何创建使用“添加为源”的 NuGet 包
好吧,令我沮丧的是,NuGet 没有内置此功能。起初,我对这个功能将作为 NuGet 3.3 和 contentFiles 功能的一部分添加的可能性感到兴奋,但不幸的是,这个功能是针对通用 Windows 项目的,而不是针对控制台应用程序、Windows Forms 或 WPF 项目的。
然而,NuGet 在安装时和卸载时都会运行一个 PowerShell 脚本,分别称为 install.psi 和 uninstall.ps1。这花了一些功夫,我甚至放弃了一次,但最终我找到了正确的库及其文档来帮助我解决这个问题。
步骤 1 – 在 Visual Studio 中创建一个 NuGet Packager 项目
- 打开 Visual Studio,然后转到 文件 | 新建项目。
注意:步骤 2 到 7 是从在线安装 NuGet Packager 项目。如果你已经完成了这些步骤,那么你很可能可以在没有这些步骤的情况下创建你的项目。 - 在右侧列表的底部,点击“Online”展开它。
注意:出于某种原因,当我点击此选项时,Visual Studio 挂起大约十到二十秒。 - 在右上角的搜索栏中,输入 NuGet。
- 选择 NuGet Packager。
- 为你的项目命名。
注意:我的项目名为 SimpleArgs.Sources。 - 为你的解决方案命名。
- 点击“确定”。
参见此图中的步骤 2 – 7
当你点击 OK 时,模板将安装。它会提示你几次,但一旦安装完成,你的项目就会被创建。注意:从现在开始,你可以在已安装 | 模板 | Visual C# | NuGet 中找到 NuGet Packager 项目。
步骤 2 – 填写 Package.nuspec 文件元数据
package.nusepc 是一个 Xml 文件。它的创建方式如下
<?xml version="1.0"?>
<package >
<metadata>
<id>SimpleArgs.Sources</id>
<version>1.0.0</version>
<title>SimpleArgs.Sources</title>
<authors>Jjbarneck</authors>
<owners></owners>
<description>A long description of the package. This shows up in the right pane of the Add Package Dialog as well as in the Package Manager Console when listing packages using the Get-Package command.</description>
<releaseNotes></releaseNotes>
<summary>A short description of the package. If specified, this shows up in the middle pane of the Add Package Dialog. If not specified, a truncated version of the description is used instead.</summary>
<language>en-US</language>
<projectUrl>https://nuget.net.cn/packages/SimpleArgs.Sources</projectUrl>
<iconUrl>https://nuget.net.cn/Content/Images/packageDefaultIcon-50x50.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<licenseUrl>https://open-source.org.cn/licenses/Apache-2.0</licenseUrl>
<copyright>Copyright 2016</copyright>
<dependencies>
<group targetFramework="net40">
<dependency id="log4net" version="1.2.10" />
</group>
</dependencies>
<references></references>
<tags></tags>
</metadata>
<files>
<file src="lib\" target="lib" />
<file src="tools\" target="tools" />
<file src="content\" target="content" />
</files>
</package>
Package.nuspec 更改
我无法遍历所有可能的 nuspec 设置。这在 Nuspec Reference 中。但是,我会给你一些我更改的基础内容。
- id – 将此设置为你的包名。如果你正确命名了你的项目,这通常已经设置正确。我将保持以上不变。
- version – 这是你的版本。如果这是你的第一个版本,1.0.0 是完美的。我将把它更改为 1.1.0,因为我之前的版本是 1.0.9。
- title – 通常与 id 相同,但不总是。我将保持不变。
- authors – 这是我。我想要 Visual Studio 用户名之外的东西。我将其更改为 Jared Barneck (Rhyous)
- owners – 这是我或我的公司。我将更改为 Rhyous Publishing LLC
- description – 长描述。这在 Xml 中定义。将其更改为描述你的 NuGet 包。
- releaseNotes – 我只放了一个指向我 GitHub 仓库中发布说明的链接:https://github.com/rhyous/SimpleArgs/blob/master/ReleaseNotes.txt
- summary – 简短描述。这也在 xml 中定义。通常比 description 短。
- language – 这是 5 位数字的 IETF 语言标记。我将其保留为 en-US。
- projectUrl – 我将其更改为我的 GitHub 位置:https://github.com/rhyous/SimpleArgs
- iconUrl – 我将其更改为我的 GitHub 源代码中的图标文件。与发布说明和许可证文件不同,我使用了图像的原始 GitHub 链接:https://raw.githubusercontent.com/rhyous/SimpleArgs/master/Docs/Images/SimpleArgs.Logo.png
- requireLicenseAcceptance – 我将其保留为 false。仅当你的许可证要求同意时才将其设置为 true。
- licenseUrl – 我将其设置为指向我 GitHub 仓库中的许可证文件
https://github.com/rhyous/SimpleArgs/blob/master/Fork%20and%20Contribute%20License.txt - copyright – 我将其设置为 Copyright Rhyous Publishing LLC
- dependencies – 此项目没有依赖项,因此我删除了整个部分。
- references – 我删除了此标签。源代码 NuGet 包可能没有任何引用。
- tags – 由于我的项目是用于命令行参数的,所以我将我的标签设置为:args, arguments
- files – 此部分已预先配置。但是,我将 libs\ 替换为 src\,因为我没有 libs,但我有源代码。
你可以在 GitHub 仓库中看到我的最终 nuspec 文件:SimpleArgs.Sources Package.nuspec
步骤 3 – 添加共享源文件
在 Visual Studio 的 Solution Explorer 中,你应该会看到已经为你提供了四个文件夹。参见右侧的图片。--->
- content – 这将复制到你的项目中。由于我们不希望复制所有源代码,所以我们不会将源代码放在这里。
注意:我会删除这个文件夹,但实际上,我有一个未共享的源文件。ArgsHandler.cs 将在每个项目中进行自定义,这很合理,因为每个项目将有不同的参数并以不同的方式处理参数。ArgsHandler.cs 将放在这里。 - libs – 我没有 libs。我可以删除这个文件夹以及 nuspec 中与之相关的 xml。
- src – 我放在这里的东西不会复制到我的项目中。我将把所有共享的源代码放在这个文件夹中。
- tools – 这里包含 PowerShell 脚本:init.ps1, install.ps1, 和 uninstall.ps1
现在我们了解了我们的文件夹结构,让我们开始工作吧。
- 在 Visual Studio 的 Solution Explorer 中,在 src 目录下创建一个名为 App_Packages 的文件夹。
注意:我本来想使用 App_Sources,但 NuGet 建议我们遵循其他社区成员的做法,而其他人已经开始将源文件放在 App_Packages 下,所以我正在遵循这个社区约定。另外,这对 PowerShell 脚本很重要,因为这个约定在其中起着作用。如果你不遵循这个约定,你将不得不编辑 install.psi 和 uninstall.psi PowerShell 脚本,我稍后会提供。 - 在 Visual Studio 的 Solution Explorer 中,创建一个包含项目名称和版本的文件夹。在我的例子中,文件夹名称是:SimpleArgs.Sources.1.1.0。
注意:同样,这是根据社区约定。其他人也在这样做。你不必完全遵循这个,同样,如果你不遵循这个约定,你将不得不编辑 install.ps1 和 uninstall.psi PowerShell 脚本,我稍后会提供。 - 在 Windows Explorer 中,而不是在 Visual Studio 中,将你的源代码放在项目名称和版本目录下。
注意:在 Visual Studio 的 Solution Explorer 中,我只有这两个目录:App_Packages/SimpleArgs.Sources.1.1.0。
注意:在 Windows Explorer 中,我的目录结构最终如下\App_Packages \App_Packages\SimpleArgs.Sources.1.1.0\ \App_Packages\SimpleArgs.Sources.1.1.0\Business \App_Packages\SimpleArgs.Sources.1.1.0\Business\Args.cs \App_Packages\SimpleArgs.Sources.1.1.0\Business\ArgsHandlerCollection.cs \App_Packages\SimpleArgs.Sources.1.1.0\Business\ArgsManager.cs \App_Packages\SimpleArgs.Sources.1.1.0\Business\ArgsReader.cs \App_Packages\SimpleArgs.Sources.1.1.0\Business\ArgumentMessageBuilder.cs \App_Packages\SimpleArgs.Sources.1.1.0\Business\CommonAllowedValues.cs \App_Packages\SimpleArgs.Sources.1.1.0\Extensions \App_Packages\SimpleArgs.Sources.1.1.0\Extensions\ArgumentExtensions.cs \App_Packages\SimpleArgs.Sources.1.1.0\Extensions\StringExtensions.cs \App_Packages\SimpleArgs.Sources.1.1.0\Interfaces \App_Packages\SimpleArgs.Sources.1.1.0\Interfaces\IArgumentMessageBuilder.cs \App_Packages\SimpleArgs.Sources.1.1.0\Interfaces\IArgumentsHandler.cs \App_Packages\SimpleArgs.Sources.1.1.0\Interfaces\IReadArgs.cs \App_Packages\SimpleArgs.Sources.1.1.0\Model \App_Packages\SimpleArgs.Sources.1.1.0\Model\Argument.cs \App_Packages\SimpleArgs.Sources.1.1.0\Model\ArgumentAddedEventArgs.cs \App_Packages\SimpleArgs.Sources.1.1.0\Model\ArgumentDictionary.cs \App_Packages\SimpleArgs.Sources.1.1.0\Model\ArgumentList.cs \App_Packages\SimpleArgs.Sources.1.1.0\Model\ArgumentsHandlerBase.cs
注意:我之所以不将这些包含在 NuGet Packager Visual Studio 项目中,是有充分理由的,我将在稍后解释。
步骤 4 – 添加源文件
如前所述,ArgsHandler.cs 文件不是共享的。每个项目都需要该文件的自己的副本。所以我们需要添加它,以便它支持 源代码转换。
- 在 Visual Studio 的 Solution Explorer 中,将任何源文件复制到 Content 目录中。你也可以将它们放在子目录中。我创建了一个 Arguments 文件夹。
- 在任何源文件末尾添加 .pp。
- 在任何源文件中,将命名空间更改为
$rootnamespace$
。你也可以像我一样在$rootnamespace$
末尾添加一个子命名空间。
using SimpleArgs;
using System;
using System.Collections.Generic;
namespace $rootnamespace$.Arguments
{
// Add this line of code to Main() in Program.cs
//
// ArgsManager.Instance.Start(new ArgsHandler(), args);
//
/// <summary>
/// A class that implements IArgumentsHandler where command line
/// arguments are defined.
/// </summary>
public sealed class ArgsHandler : ArgsHandlerBase
{
// content snipped see full file here: https://github.com/rhyous/SimpleArgs/blob/master/NuGet/SimpleArgs.NuGet/content/Arguments/ArgsHandler.cs.pp
}
}
步骤 5 – 在 NuGet 中使用 PowerShell 脚本添加为链接
有三个 PowerShell 脚本。
- init.ps1
- install.ps1
- uninstall.ps1
我们只会修改 install.ps1 和 uninstall.ps1。
注意:以下内容被编写得非常通用,并在各种 Visual Studio 项目中进行了测试,这意味着一些常见错误已被修复,例如,仅仅因为 App_Packages 已存在而不会在创建时失败。
- 更新 install.ps1。
注意:有关 install1.ps1 和 uninstall.ps1 的最新版本,请访问我 GitHub 仓库中的 tools 目录。
# Runs every time a package is uninstalled param($installPath, $toolsPath, $package, $project) # $installPath is the path to the folder where the package is installed. # $toolsPath is the path to the tools directory in the folder where the package is installed. # $package is a reference to the package object. # $project is a reference to the project the package was installed to. # Variables $src = "src" $packageName = [System.IO.Path]::GetFileName($installPath) #logging write-host "project: " $project.FullName write-host "installPath: " $installPath write-host "toolsPath: " $toolsPath write-host "package: " $package write-host "project: " $project $srcPath = [System.IO.Path]::Combine($installPath, $src) write-host "srcPath: " $srcPath $solutionDir = [System.IO.Path]::GetDirectoryName($dte.Solution.FullName) $projectDir = [System.IO.Path]::GetDirectoryName($project.FullName) write-host "solutionDir: " $solutionDir write-host "projectDir: " $projectDir $areSameDir = $solutionDir -eq $projectDir write-host "areSameDir: " $areSameDir function AddLinkedFiles($path, $addLocation, $canLink) { write-host "path: " $path write-host "addLocation: " $addLocation.FullName write-host "canLink: " $canLink foreach ($item in Get-ChildItem $path) { write-host "item: " $item $item.FullName if (Test-Path $item.FullName -PathType Container) { if ( $canLink) { $addFolder = $project.ProjectItems|Where-Object {$_.Name -eq $item} if (!$addFolder) { $addFolder = $addLocation.ProjectItems.AddFolder($item) } write-host "addFolder: " $addFolder.FullName AddLinkedFiles $item.FullName $addFolder $canLink } else { AddLinkedFiles $item.FullName $addLocation $canLink } } else { write-host "Adding " $item.FullName " to " $addLocation.FullName $addLocation.ProjectItems.AddFromFile($item.FullName) } } } write-host "Calling AddLinkedFiles" AddLinkedFiles $srcPath $project (!$areSameDir)
- 更新 uninstall.ps1。
# Runs every time a package is uninstalled param($installPath, $toolsPath, $package, $project) # $installPath is the path to the folder where the package is installed. # $toolsPath is the path to the tools directory in the folder where the package is installed. # $package is a reference to the package object. # $project is a reference to the project the package was installed to. # Variables $packages = "Packages" $app_packages = "App_Packages" $src = "src" $packageName = [System.IO.Path]::GetFileName($installPath) #logging write-host "project: " $project.FullName write-host "installPath: " $installPath write-host "toolsPath: " $toolsPath write-host "package: " $package write-host "project: " $project $srcPath = [System.IO.Path]::Combine($installPath, $src) write-host "srcPath: " $srcPath $solutionDir = [System.IO.Path]::GetDirectoryName($dte.Solution.FullName) $projectDir = [System.IO.Path]::GetDirectoryName($project.FullName) write-host "solutionDir: " $solutionDir write-host "projectDir: " $projectDir $areSameDir = $solutionDir -eq $projectDir write-host "areSameDir: " $areSameDir if ($areSameDir) { $packagesItem = $project.ProjectItems|Where-Object {$_.Name -eq $packages} write-host "packageFolder: " $packagesItem.Name $item = $packagesItem.ProjectItems|Where-Object {$_.Name -eq [System.IO.Path]::GetFileName($installPath)} write-host "item: " $item.Name $item.Remove() if ($packagesItem.ProjectItems.Count -eq 0) { $packagesItem.Remove() } } else { $app_packagesItem = $project.ProjectItems|Where-Object {$_.Name -eq $app_packages} write-host "app_packagesItem: " $app_packagesItem.Name $app_packagesFolder = [System.IO.Path]::Combine($srcPath,$app_packages) foreach ($subDir in (Get-ChildItem $app_packagesFolder)) { $item = $app_packagesItem.ProjectItems|Where-Object {$_.Name -eq $subDir.Name} write-host "item: " $item.Name if ($item) { $item.Delete() } } if ($app_packagesItem.ProjectItems.Count -eq 0 -and (Get-ChildItem ([System.IO.Path]::Combine($projectDir, $app_packages))).Count -eq 0) { $app_packagesItem.Delete() } }
步骤 6 – 生成解决方案和 NuGet 包
NuGet Packager 项目模板非常棒。当你使用它时,它会在生成时为你构建 NuGet 包。另外,如果你以 Release 模式生成,它会尝试将 NuGet 包上传到公共 NuGet Package Gallery。
- 在 Visual Studio 中,确保 Solution Configuration 设置为 Debug。
- 选择 Build | Build Solution。
- 在你的项目目录中,你应该会有一个已生成的 NuGet 包。我的包名为 SimpleArgs.Sources.1.1.1.nupkg。
敬请期待
敬请关注 NuGet for Source Using Add As Link (Part 2 – Testing & Deploying)
如果你订阅,你将不会错过任何帖子。