using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Packaging.Targets.Deb;
using Packaging.Targets.IO;
namespace Packaging.Targets
{
public class DebTask : Task
{
[Required]
public string PublishDir { get; set; }
[Required]
public string DebPath { get; set; }
[Required]
public string DebTarPath { get; set; }
[Required]
public string DebTarXzPath { get; set; }
[Required]
public string Prefix { get; set; }
[Required]
public string Version { get; set; }
[Required]
public string PackageName { get; set; }
[Required]
public ITaskItem[] Content { get; set; }
[Required]
public string Maintainer { get; set; }
[Required]
public string Description { get; set; }
///
/// Gets or sets the runtime identifier for which we are currently building.
/// Used to determine the target architecture of the package.
///
public string RuntimeIdentifier { get; set; }
///
/// Gets or sets the package architecture (amd64, i386,...). When not set,
/// the target architecture will be derived based on the
///
public string DebPackageArchitecture { get; set; }
public string Section { get; set; }
public string Homepage { get; set; }
public string Priority { get; set; }
///
/// 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.
///
public string AppHost { get; set; }
///
/// Gets or sets a list of empty folders to create when
/// installing this package.
///
public ITaskItem[] LinuxFolders { get; set; }
///
/// Gets or sets a list of Debian packages on which the version of .NET
/// Core embedded in this package depends.
///
public ITaskItem[] DebDotNetDependencies { get; set; }
///
/// Gets or sets a list of Debian packages on which this Debian
/// package depends.
///
public ITaskItem[] DebDependencies { get; set; }
///
/// Gets or sets a list of Debian packages which this Debian
/// package recommends.
///
public ITaskItem[] DebRecommends { get; set; }
///
/// Gets or sets a value indicating whether to create a Linux
/// user and group when installing the package.
///
public bool CreateUser { get; set; }
///
/// Gets or sets the name of the Linux user and group to create.
///
public string UserName { get; set; }
///
/// Gets or sets a value indicating whether to install
/// and launch as systemd service when installing the package.
///
public bool InstallService { get; set; }
///
/// Gets or sets the name of the SystemD service to create.
///
public string ServiceName { get; set; }
///
/// Gets or sets an additional pre-installation script to execute.
///
///
/// This variable must contain the script itself, and not a path to a file
/// which contains the script.
///
public string PreInstallScript { get; set; }
///
/// Gets or sets an additional post-installation script to execute.
///
///
/// This variable must contain the script itself, and not a path to a file
/// which contains the script.
///
public string PostInstallScript { get; set; }
///
/// Gets or sets an additional pre-removal script to execute.
///
///
/// This variable must contain the script itself, and not a path to a file
/// which contains the script.
///
public string PreRemoveScript { get; set; }
///
/// Gets or sets an additional post-removal script to execute.
///
///
/// This variable must contain the script itself, and not a path to a file
/// which contains the script.
///
public string PostRemoveScript { get; set; }
///
/// Derives the package architecture from a .NET runtime identiifer.
///
///
/// The runtime identifier.
///
///
/// The equivalent package architecture.
///
public static string GetPackageArchitecture(string runtimeIdentifier)
{
// Valid architectures can be obtained by running "dpkg-architecture --list-known"
RuntimeIdentifiers.ParseRuntimeId(runtimeIdentifier, out _, out _, out Architecture? architecture, out _);
if (architecture != null)
{
switch (architecture.Value)
{
case Architecture.Arm:
return "armhf";
case Architecture.Arm64:
return "arm64";
case Architecture.X64:
return "amd64";
case Architecture.X86:
return "i386";
}
}
return "all";
}
public override bool Execute()
{
this.Log.LogMessage(
MessageImportance.High,
"Creating DEB package '{0}' from folder '{1}'",
this.DebPath,
this.PublishDir);
using (var targetStream = File.Open(this.DebPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
using (var tarStream = File.Open(this.DebTarPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
{
ArchiveBuilder archiveBuilder = new ArchiveBuilder()
{
Log = this.Log,
};
var archiveEntries = archiveBuilder.FromDirectory(
this.PublishDir,
this.AppHost,
this.Prefix,
this.Content);
archiveEntries.AddRange(archiveBuilder.FromLinuxFolders(this.LinuxFolders));
EnsureDirectories(archiveEntries);
archiveEntries = archiveEntries
.OrderBy(e => e.TargetPathWithFinalSlash, StringComparer.Ordinal)
.ToList();
TarFileCreator.FromArchiveEntries(archiveEntries, tarStream);
tarStream.Position = 0;
// Prepare the list of dependencies
List dependencies = new List();
if (this.DebDependencies != null)
{
var debDependencies = this.DebDependencies.Select(d => d.ItemSpec).ToArray();
dependencies.AddRange(debDependencies);
}
if (this.DebDotNetDependencies != null)
{
var debDotNetDependencies = this.DebDotNetDependencies.Select(d => d.ItemSpec).ToArray();
dependencies.AddRange(debDotNetDependencies);
}
// Prepare the list of recommended dependencies
List recommends = new List();
if (this.DebRecommends != null)
{
recommends.AddRange(this.DebRecommends.Select(d => d.ItemSpec));
}
// XZOutputStream class has low quality (doesn't even know it's current position,
// needs to be disposed to finish compression, etc),
// So we are doing compression in a separate step
using (var tarXzStream = File.Open(this.DebTarXzPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
using (var xzStream = new XZOutputStream(tarXzStream))
{
tarStream.CopyTo(xzStream);
}
using (var tarXzStream = File.Open(this.DebTarXzPath, FileMode.Open, FileAccess.Read, FileShare.None))
{
var pkg = DebPackageCreator.BuildDebPackage(
archiveEntries,
this.PackageName,
this.Description,
this.Maintainer,
this.Version,
!string.IsNullOrWhiteSpace(this.DebPackageArchitecture) ? this.DebPackageArchitecture : GetPackageArchitecture(this.RuntimeIdentifier),
this.CreateUser,
this.UserName,
this.InstallService,
this.ServiceName,
this.Prefix,
this.Section,
this.Priority,
this.Homepage,
this.PreInstallScript,
this.PostInstallScript,
this.PreRemoveScript,
this.PostRemoveScript,
dependencies,
recommends,
null);
DebPackageCreator.WriteDebPackage(
archiveEntries,
tarXzStream,
targetStream,
pkg);
}
this.Log.LogMessage(
MessageImportance.High,
"Created DEB package '{0}' from folder '{1}'",
this.DebPath,
this.PublishDir);
return true;
}
}
internal static void EnsureDirectories(List entries, bool includeRoot = true)
{
var dirs = new HashSet(entries.Where(x => x.Mode.HasFlag(LinuxFileMode.S_IFDIR))
.Select(d => d.TargetPathWithoutFinalSlash));
var toAdd = new List();
string GetDirPath(string path)
{
path = path.TrimEnd('/');
if (path == string.Empty)
{
return "/";
}
if (!path.Contains("/"))
{
return string.Empty;
}
return path.Substring(0, path.LastIndexOf('/'));
}
void EnsureDir(string dirPath)
{
if (dirPath == string.Empty || dirPath == ".")
{
return;
}
if (!dirs.Contains(dirPath))
{
if (dirPath != "/")
{
EnsureDir(GetDirPath(dirPath));
}
dirs.Add(dirPath);
toAdd.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_IFDIR,
Modified = DateTime.Now,
Group = "root",
Owner = "root",
TargetPath = dirPath,
LinkTo = string.Empty,
});
}
}
foreach (var entry in entries)
{
EnsureDir(GetDirPath(entry.TargetPathWithFinalSlash));
}
if (includeRoot)
{
EnsureDir("/");
}
entries.AddRange(toAdd);
}
}
}