mirror of
https://github.com/quamotion/dotnet-packaging.git
synced 2026-07-08 18:06:08 +00:00
Merge pull request #135 from qmfrederik/features/bin-symlink
Install a symlink to the main executable in /usr/local/bin
This commit is contained in:
@@ -20,7 +20,7 @@ namespace Packaging.Targets.Tests
|
||||
public void FromDirectoryTest()
|
||||
{
|
||||
ArchiveBuilder builder = new ArchiveBuilder();
|
||||
var entries = builder.FromDirectory("archive", "/opt/demo", Array.Empty<ITaskItem>());
|
||||
var entries = builder.FromDirectory("archive", null, "/opt/demo", Array.Empty<ITaskItem>());
|
||||
|
||||
Assert.Equal(2, entries.Count);
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Packaging.Targets.Tests
|
||||
|
||||
var taskItem = new TaskItem("script.sh", metadata);
|
||||
var taskItems = new ITaskItem[] { taskItem };
|
||||
var entries = builder.FromDirectory("archive", "/opt/demo", taskItems);
|
||||
var entries = builder.FromDirectory("archive", null, "/opt/demo", taskItems);
|
||||
|
||||
Assert.Equal(2, entries.Count);
|
||||
|
||||
@@ -100,5 +100,56 @@ namespace Packaging.Targets.Tests
|
||||
Assert.Equal("/bin/script.sh", script.TargetPathWithFinalSlash);
|
||||
Assert.Equal(ArchiveEntryType.None, script.Type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the <see cref="ArchiveBuilder.FromDirectory(string, string, ITaskItem[])"/> method
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FromDirectoryWithAppHost()
|
||||
{
|
||||
ArchiveBuilder builder = new ArchiveBuilder();
|
||||
var entries = builder.FromDirectory("archive", "demo", "/opt/demo", Array.Empty<ITaskItem>());
|
||||
|
||||
Assert.Equal(3, entries.Count);
|
||||
|
||||
var readme = entries[0];
|
||||
Assert.Equal("root", readme.Group);
|
||||
Assert.Equal(1L, readme.Inode);
|
||||
Assert.False(readme.IsAscii);
|
||||
Assert.Equal(string.Empty, readme.LinkTo);
|
||||
Assert.Equal(LinuxFileMode.S_IROTH | LinuxFileMode.S_IRGRP | LinuxFileMode.S_IRUSR | LinuxFileMode.S_IFREG, readme.Mode);
|
||||
Assert.Equal("root", readme.Owner);
|
||||
Assert.False(readme.RemoveOnUninstall);
|
||||
Assert.Equal(Path.Combine("archive", "README.md"), readme.SourceFilename);
|
||||
Assert.Equal("/opt/demo/README.md", readme.TargetPath);
|
||||
Assert.Equal("/opt/demo/README.md", readme.TargetPathWithFinalSlash);
|
||||
Assert.Equal(ArchiveEntryType.None, readme.Type);
|
||||
|
||||
var script = entries[1];
|
||||
Assert.Equal("root", script.Group);
|
||||
Assert.Equal(2L, script.Inode);
|
||||
Assert.False(script.IsAscii);
|
||||
Assert.Equal(string.Empty, script.LinkTo);
|
||||
Assert.Equal(LinuxFileMode.S_IROTH | LinuxFileMode.S_IRGRP | LinuxFileMode.S_IRUSR | LinuxFileMode.S_IFREG, script.Mode);
|
||||
Assert.Equal("root", script.Owner);
|
||||
Assert.False(script.RemoveOnUninstall);
|
||||
Assert.Equal(Path.Combine("archive", "script.sh"), script.SourceFilename);
|
||||
Assert.Equal("/opt/demo/script.sh", script.TargetPath);
|
||||
Assert.Equal("/opt/demo/script.sh", script.TargetPathWithFinalSlash);
|
||||
Assert.Equal(ArchiveEntryType.None, script.Type);
|
||||
|
||||
var symlink = entries[2];
|
||||
Assert.Equal("root", symlink.Group);
|
||||
Assert.Equal(3L, symlink.Inode);
|
||||
Assert.False(symlink.IsAscii);
|
||||
Assert.Equal("/opt/demo/demo", symlink.LinkTo);
|
||||
Assert.Equal(LinuxFileMode.S_IXOTH | LinuxFileMode.S_IROTH | LinuxFileMode.S_IXGRP | LinuxFileMode.S_IRGRP | LinuxFileMode.S_IXUSR | LinuxFileMode.S_IWUSR | LinuxFileMode.S_IRUSR | LinuxFileMode.S_IFLNK, symlink.Mode);
|
||||
Assert.Equal("root", symlink.Owner);
|
||||
Assert.False(symlink.RemoveOnUninstall);
|
||||
Assert.Null(symlink.SourceFilename);
|
||||
Assert.Equal("/usr/local/bin/demo", symlink.TargetPath);
|
||||
Assert.Equal("/usr/local/bin/demo", symlink.TargetPathWithFinalSlash);
|
||||
Assert.Equal(ArchiveEntryType.None, symlink.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,8 +111,8 @@ namespace Packaging.Targets
|
||||
}
|
||||
else if (entry.Mode.HasFlag(LinuxFileMode.S_IFLNK))
|
||||
{
|
||||
using (var fileStrema = file.Open())
|
||||
using (var reader = new StreamReader(fileStrema, Encoding.UTF8))
|
||||
using (var fileStream = file.Open())
|
||||
using (var reader = new StreamReader(fileStream, Encoding.UTF8))
|
||||
{
|
||||
entry.LinkTo = reader.ReadToEnd();
|
||||
}
|
||||
@@ -188,10 +188,28 @@ namespace Packaging.Targets
|
||||
/// <returns>
|
||||
/// A list of <see cref="ArchiveEntry"/> objects representing the data in the directory.
|
||||
/// </returns>
|
||||
public List<ArchiveEntry> FromDirectory(string directory, string prefix, ITaskItem[] metadata)
|
||||
public List<ArchiveEntry> FromDirectory(string directory, string appHost, string prefix, ITaskItem[] metadata)
|
||||
{
|
||||
List<ArchiveEntry> value = new List<ArchiveEntry>();
|
||||
this.AddDirectory(directory, string.Empty, prefix, value, metadata);
|
||||
|
||||
// Add a symlink to appHost, if available
|
||||
if (appHost != null)
|
||||
{
|
||||
value.Add(
|
||||
new ArchiveEntry()
|
||||
{
|
||||
Mode = LinuxFileMode.S_IXOTH | LinuxFileMode.S_IROTH | LinuxFileMode.S_IXGRP | LinuxFileMode.S_IRGRP | LinuxFileMode.S_IXUSR | LinuxFileMode.S_IWUSR | LinuxFileMode.S_IRUSR | LinuxFileMode.S_IFLNK,
|
||||
Modified = DateTimeOffset.UtcNow,
|
||||
Group = "root",
|
||||
Owner = "root",
|
||||
TargetPath = $"/usr/local/bin/{appHost}",
|
||||
LinkTo = $"{prefix}/{appHost}",
|
||||
Inode = this.inode++,
|
||||
Sha256 = Array.Empty<byte>(),
|
||||
});
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -244,7 +262,7 @@ namespace Packaging.Targets
|
||||
{
|
||||
if (fileName.StartsWith(".") || fileStream.Length == 0)
|
||||
{
|
||||
// Skip hidden and empty files - this would case rmplint errors.
|
||||
// Skip hidden and empty files - this would case rpmlint errors.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,13 @@ namespace Packaging.Targets
|
||||
|
||||
public string Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path to the app host. This is a native executable which loads
|
||||
/// the .NET Core runtime, and invokes the manage assembly. On Linux, it is symlinked
|
||||
/// into ${prefix}/bin.
|
||||
/// </summary>
|
||||
public string AppHost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of empty folders to create when
|
||||
/// installing this package.
|
||||
@@ -188,6 +195,7 @@ namespace Packaging.Targets
|
||||
ArchiveBuilder archiveBuilder = new ArchiveBuilder();
|
||||
var archiveEntries = archiveBuilder.FromDirectory(
|
||||
this.PublishDir,
|
||||
this.AppHost,
|
||||
this.Prefix,
|
||||
this.Content);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Packaging.Targets.IO
|
||||
{
|
||||
@@ -43,6 +44,10 @@ namespace Packaging.Targets.IO
|
||||
{
|
||||
this.AddDirectory(entry, cpioFile);
|
||||
}
|
||||
else if (entry.Mode.HasFlag(LinuxFileMode.S_IFLNK))
|
||||
{
|
||||
this.AddSymlink(entry, cpioFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.AddFile(entry, cpioFile);
|
||||
@@ -92,6 +97,45 @@ namespace Packaging.Targets.IO
|
||||
cpioFile.Write(directoryHeader, targetPath, new MemoryStream(Array.Empty<byte>()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a symlink entry to a <see cref="CpioFile"/>.
|
||||
/// </summary>
|
||||
/// <param name="entry">
|
||||
/// The symlink entry to add.
|
||||
/// </param>
|
||||
/// <param name="cpioFile">
|
||||
/// The <see cref="CpioFile"/> to which to add the entry.
|
||||
/// </param>
|
||||
public void AddSymlink(ArchiveEntry entry, CpioFile cpioFile)
|
||||
{
|
||||
var targetPath = entry.TargetPath;
|
||||
|
||||
if (!targetPath.StartsWith("."))
|
||||
{
|
||||
targetPath = "." + targetPath;
|
||||
}
|
||||
|
||||
CpioHeader cpioHeader = new CpioHeader()
|
||||
{
|
||||
Check = 0,
|
||||
DevMajor = 1,
|
||||
DevMinor = 0,
|
||||
FileSize = entry.FileSize,
|
||||
Gid = 0, // root
|
||||
Uid = 0, // root
|
||||
Ino = entry.Inode,
|
||||
FileMode = entry.Mode,
|
||||
LastModified = entry.Modified,
|
||||
NameSize = (uint)entry.TargetPath.Length + 1,
|
||||
Nlink = 1,
|
||||
RDevMajor = 0,
|
||||
RDevMinor = 0,
|
||||
Signature = "070701",
|
||||
};
|
||||
|
||||
cpioFile.Write(cpioHeader, targetPath, new MemoryStream(Encoding.UTF8.GetBytes(entry.LinkTo)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a file entry to a <see cref="CpioFile"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -107,7 +107,7 @@ namespace Packaging.Targets.IO
|
||||
}
|
||||
else
|
||||
{
|
||||
type = TarTypeFlag.LnkType;
|
||||
type = TarTypeFlag.SymType;
|
||||
}
|
||||
|
||||
bool dispose = false;
|
||||
@@ -137,7 +137,7 @@ namespace Packaging.Targets.IO
|
||||
GroupId = 0,
|
||||
UserId = 0,
|
||||
GroupName = entry.Group,
|
||||
LinkName = string.Empty,
|
||||
LinkName = isLink ? entry.LinkTo : string.Empty,
|
||||
Prefix = string.Empty,
|
||||
TypeFlag = type,
|
||||
UserName = entry.Owner,
|
||||
|
||||
@@ -615,7 +615,7 @@ namespace Packaging.Targets.Rpm
|
||||
modes[i] = (short)file.Mode;
|
||||
rdevs[i] = file.Rdev;
|
||||
mtimes[i] = (int)file.ModifiedTime.ToUnixTimeSeconds();
|
||||
md5s[i] = BitConverter.ToString(file.MD5Hash).Replace("-", string.Empty).ToLowerInvariant();
|
||||
md5s[i] = file.MD5Hash == null ? string.Empty : BitConverter.ToString(file.MD5Hash).Replace("-", string.Empty).ToLowerInvariant();
|
||||
linkTos[i] = file.LinkTo;
|
||||
usernames[i] = file.UserName;
|
||||
groupnames[i] = file.GroupName;
|
||||
|
||||
@@ -524,6 +524,16 @@ namespace Packaging.Targets.Rpm
|
||||
size = 0x1000;
|
||||
}
|
||||
|
||||
if (entry.Mode.HasFlag(LinuxFileMode.S_IFLNK))
|
||||
{
|
||||
// RPM uses cpio archives. cpio archives store the target of a symlink as
|
||||
// the file content (as opposed to tar, which has a dedicated field for this).
|
||||
// As a result, the file size is equal to the length of the path of the link
|
||||
// target.
|
||||
// https://bugzilla.redhat.com/show_bug.cgi?id=1224555 has some background info.
|
||||
size = (uint)Encoding.UTF8.GetByteCount(entry.LinkTo);
|
||||
}
|
||||
|
||||
RpmFile file = new RpmFile()
|
||||
{
|
||||
Size = (int)size,
|
||||
|
||||
@@ -80,6 +80,13 @@ namespace Packaging.Targets
|
||||
/// </summary>
|
||||
public string RpmPackageArchitecture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path to the app host. This is a native executable which loads
|
||||
/// the .NET Core runtime, and invokes the manage assembly. On Linux, it is symlinked
|
||||
/// into ${prefix}/bin.
|
||||
/// </summary>
|
||||
public string AppHost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of empty folders to create when
|
||||
/// installing this package.
|
||||
@@ -263,10 +270,12 @@ namespace Packaging.Targets
|
||||
ArchiveBuilder archiveBuilder = new ArchiveBuilder();
|
||||
var archiveEntries = archiveBuilder.FromDirectory(
|
||||
this.PublishDir,
|
||||
this.AppHost,
|
||||
this.Prefix,
|
||||
this.Content);
|
||||
|
||||
archiveEntries.AddRange(archiveBuilder.FromLinuxFolders(this.LinuxFolders));
|
||||
|
||||
archiveEntries = archiveEntries
|
||||
.OrderBy(e => e.TargetPathWithFinalSlash, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace Packaging.Targets
|
||||
ArchiveBuilder archiveBuilder = new ArchiveBuilder();
|
||||
var archiveEntries = archiveBuilder.FromDirectory(
|
||||
this.PublishDir,
|
||||
null,
|
||||
this.Prefix,
|
||||
this.Content);
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
<PackageMaintainer Condition="'$(PackageMaintainer)' == '' AND '$(Authors)' == ''">Anonymous <noreply@example.com></PackageMaintainer>
|
||||
<PackageDescription Condition="'$(PackageDescription)' == '' AND '$(Description)' != ''">$(Description)</PackageDescription>
|
||||
<PackageDescription Condition="'$(PackageDescription)' == '' AND '$(Description)' == ''">$(PackageName) version $(PackageVersion) - $(Release)</PackageDescription>
|
||||
<SymlinkAppHostInBin Condition="'$(SymlinkAppHostInBin)' == ''">true</SymlinkAppHostInBin>
|
||||
<AppHost Condition="'$(AppHost)' == '' AND '$(SymlinkAppHostInBin)' == 'true'">$(AssemblyName)$(_NativeExecutableExtension)</AppHost>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
@@ -90,6 +92,7 @@
|
||||
Version="$(PackageVersion)"
|
||||
Release="$(Release)"
|
||||
PackageName="$(PackageName)"
|
||||
AppHost="$(AppHost)"
|
||||
Content="@(Content)"
|
||||
LinuxFolders="@(LinuxFolder)"
|
||||
RpmDotNetDependencies="@(RpmDotNetDependency)"
|
||||
@@ -158,6 +161,7 @@
|
||||
Prefix="$(Prefix)"
|
||||
Version="$(PackageVersion)"
|
||||
PackageName="$(PackageName)"
|
||||
AppHost="$(AppHost)"
|
||||
Content="@(Content)"
|
||||
LinuxFolders="@(LinuxFolder)"
|
||||
DebDependencies="@(DebDependency)"
|
||||
|
||||
@@ -8,3 +8,7 @@ testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
|
||||
|
||||
def test_package_is_runnable(host):
|
||||
host.run_test("/usr/share/framework-dependent-app/framework-dependent-app")
|
||||
|
||||
|
||||
def test_package_symlink_is_runnable(host):
|
||||
host.run_test("/usr/local/bin/framework-dependent-app")
|
||||
|
||||
@@ -8,3 +8,7 @@ testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
|
||||
|
||||
def test_package_is_runnable(host):
|
||||
host.run_test("/usr/share/self-contained-app/self-contained-app")
|
||||
|
||||
|
||||
def test_package_symlink_is_runnable(host):
|
||||
host.run_test("/usr/local/bin/self-contained-app")
|
||||
|
||||
Reference in New Issue
Block a user