From 21d04d6ee7a9cd527969723f94767c30bde1acf9 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 27 Dec 2023 11:29:59 +0800 Subject: [PATCH 01/74] =?UTF-8?q?=E6=96=B0=E5=BB=BA=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E7=BB=99=20UOS=20=E4=BD=BF=E7=94=A8=E7=9A=84=20deb=20=E5=8C=85?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/Build/package.props | 2 + DebUOS/Packing.DebUOS/Build/package.targets | 2 + DebUOS/Packing.DebUOS/Packing.DebUOS.csproj | 43 +++++++++++++++++++++ DebUOS/Packing.DebUOS/Program.cs | 14 +++++++ DotNETBuild.sln | 17 ++++++++ 5 files changed, 78 insertions(+) create mode 100644 DebUOS/Packing.DebUOS/Build/package.props create mode 100644 DebUOS/Packing.DebUOS/Build/package.targets create mode 100644 DebUOS/Packing.DebUOS/Packing.DebUOS.csproj create mode 100644 DebUOS/Packing.DebUOS/Program.cs diff --git a/DebUOS/Packing.DebUOS/Build/package.props b/DebUOS/Packing.DebUOS/Build/package.props new file mode 100644 index 0000000..c1df222 --- /dev/null +++ b/DebUOS/Packing.DebUOS/Build/package.props @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/DebUOS/Packing.DebUOS/Build/package.targets b/DebUOS/Packing.DebUOS/Build/package.targets new file mode 100644 index 0000000..c1df222 --- /dev/null +++ b/DebUOS/Packing.DebUOS/Build/package.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj b/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj new file mode 100644 index 0000000..19722a0 --- /dev/null +++ b/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj @@ -0,0 +1,43 @@ + + + + Exe + net6.0 + enable + True + + + tools + + true + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + MIT + true + + + + + + + all + + + all + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + diff --git a/DebUOS/Packing.DebUOS/Program.cs b/DebUOS/Packing.DebUOS/Program.cs new file mode 100644 index 0000000..df69a24 --- /dev/null +++ b/DebUOS/Packing.DebUOS/Program.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Packing.DebUOS; + +internal class Program +{ + public static void Main(string[] args) + { + } +} diff --git a/DotNETBuild.sln b/DotNETBuild.sln index 5dcc683..3802335 100644 --- a/DotNETBuild.sln +++ b/DotNETBuild.sln @@ -89,6 +89,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SyncTool", "SyncTool", "{3C EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SyncTool", "SyncTool\SyncTool.csproj", "{4360E6F1-680C-45D7-A4E0-1663A237709A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DebUOS", "DebUOS", "{AC990428-ACB7-46A9-B66A-AF0557A7D0C6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Packing.DebUOS", "DebUOS\Packing.DebUOS\Packing.DebUOS.csproj", "{209825D6-7821-4E0E-B3C8-E3504FF65B36}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -471,6 +475,18 @@ Global {4360E6F1-680C-45D7-A4E0-1663A237709A}.Release|x64.Build.0 = Release|Any CPU {4360E6F1-680C-45D7-A4E0-1663A237709A}.Release|x86.ActiveCfg = Release|Any CPU {4360E6F1-680C-45D7-A4E0-1663A237709A}.Release|x86.Build.0 = Release|Any CPU + {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Debug|x64.ActiveCfg = Debug|Any CPU + {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Debug|x64.Build.0 = Debug|Any CPU + {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Debug|x86.ActiveCfg = Debug|Any CPU + {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Debug|x86.Build.0 = Debug|Any CPU + {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|Any CPU.Build.0 = Release|Any CPU + {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|x64.ActiveCfg = Release|Any CPU + {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|x64.Build.0 = Release|Any CPU + {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|x86.ActiveCfg = Release|Any CPU + {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -491,6 +507,7 @@ Global {E252DBCC-6FC9-42FF-86DB-E31D076F954F} = {2C43AF3C-C3B2-4B8A-81E2-DA6D8A53356A} {93037B85-0A27-4A6E-B485-2F4544ABEFDF} = {2C43AF3C-C3B2-4B8A-81E2-DA6D8A53356A} {4360E6F1-680C-45D7-A4E0-1663A237709A} = {3C8DD4B9-55DB-450E-B06A-B7C4EB3A9CF1} + {209825D6-7821-4E0E-B3C8-E3504FF65B36} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A6E377C6-EDE0-4135-951F-78C62B63DE96} From 08c1acc9f710b68333343174dca67569f335f3be Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 27 Dec 2023 11:37:29 +0800 Subject: [PATCH 02/74] Copy code from quamotion/dotnet-packaging https://github.com/quamotion/dotnet-packaging https://github.com/quamotion/dotnet-packaging/issues/244 --- DebUOS/Packaging.Targets/ArchiveBuilder.cs | 445 +++++++ .../Packaging.Targets/AssemblyAttributes.cs | 3 + .../Packaging.Targets/CopyToDirectoryValue.cs | 23 + .../Deb/ControlFileParser.cs | 67 ++ DebUOS/Packaging.Targets/Deb/DebPackage.cs | 41 + .../Deb/DebPackageControlFileData.cs | 15 + .../Deb/DebPackageCreator.cs | 243 ++++ .../Packaging.Targets/Deb/DebPackageReader.cs | 157 +++ DebUOS/Packaging.Targets/DebTask.cs | 363 ++++++ DebUOS/Packaging.Targets/IO/ArFile.cs | 94 ++ DebUOS/Packaging.Targets/IO/ArFileCreator.cs | 44 + DebUOS/Packaging.Targets/IO/ArHeader.cs | 141 +++ DebUOS/Packaging.Targets/IO/ArchiveEntry.cs | 132 +++ .../Packaging.Targets/IO/ArchiveEntryType.cs | 23 + DebUOS/Packaging.Targets/IO/ArchiveFile.cs | 175 +++ DebUOS/Packaging.Targets/IO/ConcatStream.cs | 271 +++++ DebUOS/Packaging.Targets/IO/CpioFile.cs | 187 +++ .../Packaging.Targets/IO/CpioFileCreator.cs | 181 +++ DebUOS/Packaging.Targets/IO/CpioHeader.cs | 262 +++++ DebUOS/Packaging.Targets/IO/DlOpenFlags.cs | 70 ++ DebUOS/Packaging.Targets/IO/Extensions.cs | 21 + .../Packaging.Targets/IO/GZipDecompressor.cs | 34 + DebUOS/Packaging.Targets/IO/IArchiveHeader.cs | 25 + DebUOS/Packaging.Targets/IO/LinuxFileMode.cs | 122 ++ DebUOS/Packaging.Targets/IO/LzmaAction.cs | 136 +++ DebUOS/Packaging.Targets/IO/LzmaCheck.cs | 34 + .../Packaging.Targets/IO/LzmaDecodeFlags.cs | 47 + DebUOS/Packaging.Targets/IO/LzmaMT.cs | 154 +++ DebUOS/Packaging.Targets/IO/LzmaResult.cs | 230 ++++ DebUOS/Packaging.Targets/IO/LzmaStream.cs | 121 ++ .../Packaging.Targets/IO/LzmaStreamFlags.cs | 98 ++ DebUOS/Packaging.Targets/IO/NativeMethods.cs | 407 +++++++ DebUOS/Packaging.Targets/IO/SubStream.cs | 380 ++++++ DebUOS/Packaging.Targets/IO/TarFile.cs | 86 ++ DebUOS/Packaging.Targets/IO/TarFileCreator.cs | 176 +++ DebUOS/Packaging.Targets/IO/TarHeader.cs | 267 +++++ DebUOS/Packaging.Targets/IO/TarTypeFlag.cs | 23 + DebUOS/Packaging.Targets/IO/XZInputStream.cs | 333 ++++++ DebUOS/Packaging.Targets/IO/XZOutputStream.cs | 339 ++++++ .../Native/FunctionLoader.cs | 136 +++ .../Native/LinuxNativeMethods.cs | 18 + .../Native/MacNativeMethods.cs | 18 + .../Native/WindowsNativeMethods.cs | 48 + .../Packaging.Targets.csproj | 93 ++ .../Packaging.Targets/Rpm/ChangelogEntry.cs | 69 ++ .../Rpm/CollectionExtensions.cs | 31 + DebUOS/Packaging.Targets/Rpm/DefaultOrder.cs | 113 ++ .../Rpm/DependencyAttribute.cs | 20 + DebUOS/Packaging.Targets/Rpm/ElfClass.cs | 18 + DebUOS/Packaging.Targets/Rpm/ElfFile.cs | 52 + DebUOS/Packaging.Targets/Rpm/ElfHeader.cs | 46 + DebUOS/Packaging.Targets/Rpm/ElfMachine.cs | 65 ++ DebUOS/Packaging.Targets/Rpm/ElfType.cs | 28 + DebUOS/Packaging.Targets/Rpm/FileAnalyzer.cs | 131 +++ DebUOS/Packaging.Targets/Rpm/IFileAnalyzer.cs | 68 ++ .../Packaging.Targets/Rpm/IPackageSigner.cs | 13 + DebUOS/Packaging.Targets/Rpm/IndexHeader.cs | 29 + DebUOS/Packaging.Targets/Rpm/IndexRecord.cs | 26 + DebUOS/Packaging.Targets/Rpm/IndexTag.cs | 665 +++++++++++ DebUOS/Packaging.Targets/Rpm/IndexType.cs | 16 + .../Rpm/PackageDependency.cs | 92 ++ DebUOS/Packaging.Targets/Rpm/PackageSigner.cs | 30 + DebUOS/Packaging.Targets/Rpm/PgpHashAlgo.cs | 37 + DebUOS/Packaging.Targets/Rpm/PgpSigner.cs | 177 +++ DebUOS/Packaging.Targets/Rpm/RpmDumper.cs | 86 ++ DebUOS/Packaging.Targets/Rpm/RpmFile.cs | 182 +++ DebUOS/Packaging.Targets/Rpm/RpmFileColor.cs | 44 + DebUOS/Packaging.Targets/Rpm/RpmFileFlags.cs | 20 + DebUOS/Packaging.Targets/Rpm/RpmHeader.cs | 29 + DebUOS/Packaging.Targets/Rpm/RpmLead.cs | 70 ++ DebUOS/Packaging.Targets/Rpm/RpmMetadata.cs | 1019 +++++++++++++++++ DebUOS/Packaging.Targets/Rpm/RpmPackage.cs | 49 + .../Rpm/RpmPackageCreator.cs | 949 +++++++++++++++ .../Packaging.Targets/Rpm/RpmPackageReader.cs | 208 ++++ .../Packaging.Targets/Rpm/RpmPackageWriter.cs | 197 ++++ .../Packaging.Targets/Rpm/RpmPayloadReader.cs | 125 ++ DebUOS/Packaging.Targets/Rpm/RpmSense.cs | 35 + DebUOS/Packaging.Targets/Rpm/RpmSignature.cs | 372 ++++++ .../Packaging.Targets/Rpm/RpmVerifyFlags.cs | 26 + DebUOS/Packaging.Targets/Rpm/Section.cs | 17 + DebUOS/Packaging.Targets/Rpm/SignatureTag.cs | 58 + DebUOS/Packaging.Targets/RpmTask.cs | 353 ++++++ .../Packaging.Targets/RuntimeIdentifiers.cs | 125 ++ DebUOS/Packaging.Targets/StreamExtensions.cs | 189 +++ DebUOS/Packaging.Targets/TarballTask.cs | 64 ++ .../Packaging.Targets/TaskItemExtensions.cs | 185 +++ DebUOS/Packaging.Targets/ZipTask.cs | 68 ++ .../build/Packaging.Targets.targets | 285 +++++ DebUOS/Packaging.Targets/build/Product.wxs | 31 + .../dotnet-packaging.ruleset | 73 ++ .../runtimes/win7-x64/native/lzma.dll | Bin 0 -> 150528 bytes DebUOS/Packaging.Targets/stylecop.json | 8 + 92 files changed, 12876 insertions(+) create mode 100644 DebUOS/Packaging.Targets/ArchiveBuilder.cs create mode 100644 DebUOS/Packaging.Targets/AssemblyAttributes.cs create mode 100644 DebUOS/Packaging.Targets/CopyToDirectoryValue.cs create mode 100644 DebUOS/Packaging.Targets/Deb/ControlFileParser.cs create mode 100644 DebUOS/Packaging.Targets/Deb/DebPackage.cs create mode 100644 DebUOS/Packaging.Targets/Deb/DebPackageControlFileData.cs create mode 100644 DebUOS/Packaging.Targets/Deb/DebPackageCreator.cs create mode 100644 DebUOS/Packaging.Targets/Deb/DebPackageReader.cs create mode 100644 DebUOS/Packaging.Targets/DebTask.cs create mode 100644 DebUOS/Packaging.Targets/IO/ArFile.cs create mode 100644 DebUOS/Packaging.Targets/IO/ArFileCreator.cs create mode 100644 DebUOS/Packaging.Targets/IO/ArHeader.cs create mode 100644 DebUOS/Packaging.Targets/IO/ArchiveEntry.cs create mode 100644 DebUOS/Packaging.Targets/IO/ArchiveEntryType.cs create mode 100644 DebUOS/Packaging.Targets/IO/ArchiveFile.cs create mode 100644 DebUOS/Packaging.Targets/IO/ConcatStream.cs create mode 100644 DebUOS/Packaging.Targets/IO/CpioFile.cs create mode 100644 DebUOS/Packaging.Targets/IO/CpioFileCreator.cs create mode 100644 DebUOS/Packaging.Targets/IO/CpioHeader.cs create mode 100644 DebUOS/Packaging.Targets/IO/DlOpenFlags.cs create mode 100644 DebUOS/Packaging.Targets/IO/Extensions.cs create mode 100644 DebUOS/Packaging.Targets/IO/GZipDecompressor.cs create mode 100644 DebUOS/Packaging.Targets/IO/IArchiveHeader.cs create mode 100644 DebUOS/Packaging.Targets/IO/LinuxFileMode.cs create mode 100644 DebUOS/Packaging.Targets/IO/LzmaAction.cs create mode 100644 DebUOS/Packaging.Targets/IO/LzmaCheck.cs create mode 100644 DebUOS/Packaging.Targets/IO/LzmaDecodeFlags.cs create mode 100644 DebUOS/Packaging.Targets/IO/LzmaMT.cs create mode 100644 DebUOS/Packaging.Targets/IO/LzmaResult.cs create mode 100644 DebUOS/Packaging.Targets/IO/LzmaStream.cs create mode 100644 DebUOS/Packaging.Targets/IO/LzmaStreamFlags.cs create mode 100644 DebUOS/Packaging.Targets/IO/NativeMethods.cs create mode 100644 DebUOS/Packaging.Targets/IO/SubStream.cs create mode 100644 DebUOS/Packaging.Targets/IO/TarFile.cs create mode 100644 DebUOS/Packaging.Targets/IO/TarFileCreator.cs create mode 100644 DebUOS/Packaging.Targets/IO/TarHeader.cs create mode 100644 DebUOS/Packaging.Targets/IO/TarTypeFlag.cs create mode 100644 DebUOS/Packaging.Targets/IO/XZInputStream.cs create mode 100644 DebUOS/Packaging.Targets/IO/XZOutputStream.cs create mode 100644 DebUOS/Packaging.Targets/Native/FunctionLoader.cs create mode 100644 DebUOS/Packaging.Targets/Native/LinuxNativeMethods.cs create mode 100644 DebUOS/Packaging.Targets/Native/MacNativeMethods.cs create mode 100644 DebUOS/Packaging.Targets/Native/WindowsNativeMethods.cs create mode 100644 DebUOS/Packaging.Targets/Packaging.Targets.csproj create mode 100644 DebUOS/Packaging.Targets/Rpm/ChangelogEntry.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/CollectionExtensions.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/DefaultOrder.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/DependencyAttribute.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/ElfClass.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/ElfFile.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/ElfHeader.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/ElfMachine.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/ElfType.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/FileAnalyzer.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/IFileAnalyzer.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/IPackageSigner.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/IndexHeader.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/IndexRecord.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/IndexTag.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/IndexType.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/PackageDependency.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/PackageSigner.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/PgpHashAlgo.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/PgpSigner.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmDumper.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmFile.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmFileColor.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmFileFlags.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmHeader.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmLead.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmMetadata.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmPackage.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmPackageCreator.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmPackageReader.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmPackageWriter.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmPayloadReader.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmSense.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmSignature.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/RpmVerifyFlags.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/Section.cs create mode 100644 DebUOS/Packaging.Targets/Rpm/SignatureTag.cs create mode 100644 DebUOS/Packaging.Targets/RpmTask.cs create mode 100644 DebUOS/Packaging.Targets/RuntimeIdentifiers.cs create mode 100644 DebUOS/Packaging.Targets/StreamExtensions.cs create mode 100644 DebUOS/Packaging.Targets/TarballTask.cs create mode 100644 DebUOS/Packaging.Targets/TaskItemExtensions.cs create mode 100644 DebUOS/Packaging.Targets/ZipTask.cs create mode 100644 DebUOS/Packaging.Targets/build/Packaging.Targets.targets create mode 100644 DebUOS/Packaging.Targets/build/Product.wxs create mode 100644 DebUOS/Packaging.Targets/dotnet-packaging.ruleset create mode 100644 DebUOS/Packaging.Targets/runtimes/win7-x64/native/lzma.dll create mode 100644 DebUOS/Packaging.Targets/stylecop.json diff --git a/DebUOS/Packaging.Targets/ArchiveBuilder.cs b/DebUOS/Packaging.Targets/ArchiveBuilder.cs new file mode 100644 index 0000000..9f038be --- /dev/null +++ b/DebUOS/Packaging.Targets/ArchiveBuilder.cs @@ -0,0 +1,445 @@ +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Packaging.Targets.IO; +using Packaging.Targets.Rpm; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace Packaging.Targets +{ + /// + /// Creates a list of objects based on the publish directory of a .NET Core application. + /// + internal class ArchiveBuilder + { + private IFileAnalyzer fileAnayzer; + private uint inode = 0; + + /// + /// Initializes a new instance of the class. + /// + public ArchiveBuilder() + { + this.fileAnayzer = new FileAnalyzer(); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A which can extract item metadata. + /// + public ArchiveBuilder(IFileAnalyzer analyzer) + { + if (analyzer == null) + { + throw new ArgumentNullException(nameof(analyzer)); + } + + this.fileAnayzer = analyzer; + } + + /// + /// Gets or sets a to which status messages can be written. + /// + public TaskLoggingHelper Log + { + get; + set; + } + + /// + /// Extracts the objects from a CPIO file. + /// + /// + /// The CPIO file from which to extract the entries. + /// + /// + /// A list of objects representing the data in the CPIO file. + /// + public List FromCpio(CpioFile file) + { + List value = new List(); + byte[] buffer = new byte[1024]; + byte[] fileHeader = null; + + while (file.Read()) + { + fileHeader = null; + + ArchiveEntry entry = new ArchiveEntry() + { + FileSize = file.EntryHeader.FileSize, + Group = "root", + Owner = "root", + Inode = file.EntryHeader.Ino, + Mode = file.EntryHeader.FileMode, + Modified = file.EntryHeader.LastModified, + TargetPath = file.FileName, + Type = ArchiveEntryType.None, + LinkTo = string.Empty, + Sha256 = Array.Empty(), + SourceFilename = null, + IsAscii = true + }; + + if (entry.Mode.HasFlag(LinuxFileMode.S_IFREG) && !entry.Mode.HasFlag(LinuxFileMode.S_IFLNK)) + { + using (var fileStream = file.Open()) + using (var hasher = IncrementalHash.CreateHash(HashAlgorithmName.SHA256)) + { + int read; + + while (true) + { + read = fileStream.Read(buffer, 0, buffer.Length); + + if (fileHeader == null) + { + fileHeader = new byte[read]; + Buffer.BlockCopy(buffer, 0, fileHeader, 0, read); + } + + hasher.AppendData(buffer, 0, read); + entry.IsAscii = entry.IsAscii && fileHeader.All(c => c < 128); + + if (read < buffer.Length) + { + break; + } + } + + entry.Sha256 = hasher.GetHashAndReset(); + } + + entry.Type = this.GetArchiveEntryType(fileHeader); + } + else if (entry.Mode.HasFlag(LinuxFileMode.S_IFLNK)) + { + using (var fileStream = file.Open()) + using (var reader = new StreamReader(fileStream, Encoding.UTF8)) + { + entry.LinkTo = reader.ReadToEnd(); + } + } + else + { + file.Skip(); + } + + if (entry.Mode.HasFlag(LinuxFileMode.S_IFDIR)) + { + entry.FileSize = 0x1000; + } + + if (entry.TargetPath.StartsWith(".")) + { + entry.TargetPath = entry.TargetPath.Substring(1); + } + + value.Add(entry); + } + + return value; + } + + public Collection FromLinuxFolders(ITaskItem[] metadata) + { + Collection value = new Collection(); + + // This can be null if the user did not define any folders. + // In that case: nothing to do. + if (metadata != null) + { + foreach (var folder in metadata) + { + var path = folder.ItemSpec.Replace("\\", "/"); + + // Default file mode + LinuxFileMode 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; + mode = this.GetFileMode(path, folder, mode); + + // Write out an entry for the directory + ArchiveEntry directoryEntry = new ArchiveEntry() + { + FileSize = 0x00001000, + Sha256 = Array.Empty(), + Mode = mode, + Modified = DateTime.Now, + Group = folder.GetGroup(), + Owner = folder.GetOwner(), + Inode = this.inode++, + TargetPath = path, + LinkTo = string.Empty, + RemoveOnUninstall = folder.GetRemoveOnUninstall() + }; + + value.Add(directoryEntry); + } + } + + return value; + } + + /// + /// Extracts the objects from a directory. + /// + /// + /// The directory from which to extract the entries. + /// + /// + /// The prefix of the target directory. + /// + /// + /// Additional metadata to use. + /// + /// + /// A list of objects representing the data in the directory. + /// + public List FromDirectory(string directory, string appHost, string prefix, ITaskItem[] metadata) + { + List value = new List(); + 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(), + }); + } + + return value; + } + + protected void AddDirectory(string directory, string relativePath, string prefix, List value, ITaskItem[] metadata) + { + this.inode++; + + // The order in which the files appear in the cpio archive is important; if this is not respected xzdio + // will report errors like: + // error: unpacking of archive failed on file ./usr/share/quamotion/mscorlib.dll: cpio: Archive file not in header + var entries = Directory.GetFileSystemEntries(directory).OrderBy(e => Directory.Exists(e) ? e + "/" : e, StringComparer.Ordinal).ToArray(); + + foreach (var entry in entries) + { + if (File.Exists(entry)) + { + this.AddFile(entry, relativePath + Path.GetFileName(entry), prefix, value, metadata); + } + else + { + this.AddDirectory(entry, relativePath + Path.GetFileName(entry) + "/", prefix + "/" + Path.GetFileName(entry), value, metadata); + } + } + } + + protected void AddFile(string entry, string relativePath, string prefix, List value, ITaskItem[] metadata) + { + var fileName = Path.GetFileName(entry); + + byte[] fileHeader = null; + byte[] hash = null; + byte[] md5hash = null; + byte[] buffer = new byte[1024]; + bool isAscii = true; + + var fileMetadata = metadata.SingleOrDefault(m => m.IsPublished() && string.Equals(relativePath, m.GetPublishedPath())); + + using (Stream fileStream = File.OpenRead(entry)) + { + // Skip hidden and empty files - this would case rpmlint errors. + if (fileName.StartsWith(".")) + { + this.Log.LogWarning($"Ignoring file {relativePath} because it starts with the '.' character and is considered a hidden file."); + return; + } + + if (fileStream.Length == 0) + { + this.Log.LogWarning($"Ignoring file {relativePath} because it is empty."); + return; + } + + using (var hasher = IncrementalHash.CreateHash(HashAlgorithmName.SHA256)) + using (var md5hasher = IncrementalHash.CreateHash(HashAlgorithmName.MD5)) + { + int read; + + while (true) + { + read = fileStream.Read(buffer, 0, buffer.Length); + + if (fileHeader == null) + { + fileHeader = new byte[read]; + Buffer.BlockCopy(buffer, 0, fileHeader, 0, read); + } + + hasher.AppendData(buffer, 0, read); + md5hasher.AppendData(buffer, 0, read); + isAscii = isAscii && buffer.All(c => c < 128); + + if (read < buffer.Length) + { + break; + } + } + + hash = hasher.GetHashAndReset(); + md5hash = md5hasher.GetHashAndReset(); + } + + // Only support ELF32 and ELF64 colors; otherwise default to BLACK. + ArchiveEntryType entryType = this.GetArchiveEntryType(fileHeader); + + var mode = LinuxFileMode.S_IROTH | LinuxFileMode.S_IRGRP | LinuxFileMode.S_IRUSR | LinuxFileMode.S_IFREG; + + if (entryType == ArchiveEntryType.Executable32 || entryType == ArchiveEntryType.Executable64) + { + mode |= LinuxFileMode.S_IXOTH | LinuxFileMode.S_IXGRP | LinuxFileMode.S_IWUSR | LinuxFileMode.S_IXUSR; + } + + // If a Linux path has been specified, use that one, else, use the default one based on the prefix + // + current file name. + string name = fileMetadata?.GetLinuxPath(); + + if (name == null) + { + if (!string.IsNullOrEmpty(prefix)) + { + name = prefix + "/" + fileName; + } + else + { + name = fileName; + } + } + + string linkTo = string.Empty; + + if (mode.HasFlag(LinuxFileMode.S_IFLNK)) + { + // Find the link text + int stringEnd = 0; + + while (stringEnd < fileHeader.Length - 1 && fileHeader[stringEnd] != 0) + { + stringEnd++; + } + + linkTo = Encoding.UTF8.GetString(fileHeader, 0, stringEnd + 1); + hash = new byte[] { }; + } + + mode = this.GetFileMode(name, fileMetadata, mode); + + ArchiveEntry archiveEntry = new ArchiveEntry() + { + FileSize = (uint)fileStream.Length, + Group = fileMetadata.GetGroup(), + Owner = fileMetadata.GetOwner(), + Modified = File.GetLastWriteTimeUtc(entry), + SourceFilename = entry, + TargetPath = name, + Sha256 = hash, + Md5Hash = md5hash, + Type = entryType, + LinkTo = linkTo, + Inode = this.inode++, + IsAscii = isAscii, + Mode = mode + }; + + value.Add(archiveEntry); + } + } + + private ArchiveEntryType GetArchiveEntryType(byte[] fileHeader) + { + if (ElfFile.IsElfFile(fileHeader)) + { + ElfHeader elfHeader = ElfFile.ReadHeader(fileHeader); + + if (elfHeader.@class == ElfClass.Elf32) + { + return ArchiveEntryType.Executable32; + } + else + { + return ArchiveEntryType.Executable64; + } + } + + return ArchiveEntryType.None; + } + + /// + /// Gets the file mode for a file or directory entry, based on a default value + /// and the value of the LinuxFileMode attribute, if set. + /// + /// + /// The name (path) of the entry. Only used in error messages. + /// + /// + /// The metadata for the current entry. + /// + /// + /// The default mode. This mode is used to determine the file type (directory/file), + /// and when no LinuxFileMode value is specified. + /// + /// + /// The for the current entry. + /// + private LinuxFileMode GetFileMode(string name, ITaskItem metadata, LinuxFileMode defaultMode) + { + LinuxFileMode mode = defaultMode; + LinuxFileMode defaultFileTypeMask = defaultMode & LinuxFileMode.FileTypeMask; + + // If the user has chosen to override the file node, respect that + var overridenFileMode = metadata?.GetLinuxFileMode(); + + if (overridenFileMode != null) + { + // We expect the user to specify the file mode in its octal representation, + // such as 755. We don't expect users to specify the higher bits (e.g. + // S_IFREG). + try + { + mode = (LinuxFileMode)Convert.ToUInt32(overridenFileMode, 8); + + LinuxFileMode fileType = mode & LinuxFileMode.FileTypeMask; + + if (fileType != LinuxFileMode.None && fileType != defaultFileTypeMask) + { + this.Log.LogWarning($"An invalid file type of '{fileType}' has been set for file '{name}'. The file type will be reset to {defaultFileTypeMask}."); + } + + // Override the file type mask and hardcode it to the file type mask from the default mode. + // In practice this will ensure the S_IFREG or S_IFDIR flag is set. + mode = (mode & ~LinuxFileMode.FileTypeMask) | defaultFileTypeMask; + } + catch (Exception) + { + throw new Exception($"Could not parse the file mode '{overridenFileMode}' for file '{name}'. Make sure to set the LinuxFileMode attriubute to an octal representation of a Unix file mode."); + } + } + + return mode; + } + } +} diff --git a/DebUOS/Packaging.Targets/AssemblyAttributes.cs b/DebUOS/Packaging.Targets/AssemblyAttributes.cs new file mode 100644 index 0000000..ab9c458 --- /dev/null +++ b/DebUOS/Packaging.Targets/AssemblyAttributes.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Packaging.Targets.Tests")] \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/CopyToDirectoryValue.cs b/DebUOS/Packaging.Targets/CopyToDirectoryValue.cs new file mode 100644 index 0000000..9bd9324 --- /dev/null +++ b/DebUOS/Packaging.Targets/CopyToDirectoryValue.cs @@ -0,0 +1,23 @@ +namespace Packaging.Targets +{ + /// + /// Determines whether a file is copied to its output directory or not. + /// + internal enum CopyToDirectoryValue + { + /// + /// The file is never copied. + /// + DoNotCopy, + + /// + /// The file is alwasy copied. + /// + Always, + + /// + /// The file is copied only if it is newer. + /// + PreserveNewest + } +} diff --git a/DebUOS/Packaging.Targets/Deb/ControlFileParser.cs b/DebUOS/Packaging.Targets/Deb/ControlFileParser.cs new file mode 100644 index 0000000..884fe11 --- /dev/null +++ b/DebUOS/Packaging.Targets/Deb/ControlFileParser.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Packaging.Targets.Deb +{ + /// + /// Supports reading and writing Debian control files. + /// + /// + internal static class ControlFileParser + { + /// + /// Reads a control file from a . + /// + /// + /// A which represents the control file. + /// + /// + /// A which represents the contents of the control file. + /// + internal static Dictionary Read(Stream stream) + { + Dictionary values = new Dictionary(); + + using (StreamReader reader = new StreamReader(stream, Encoding.UTF8, false, bufferSize: 1024, leaveOpen: true)) + { + string line; + string currentKey = null; + + while (reader.Peek() > 0) + { + line = reader.ReadLine(); + + if (line.StartsWith("#")) + { + continue; + } + + if (line.StartsWith(" ") || line.StartsWith("\t")) + { + // Continuation line + var value = values[currentKey]; + value += '\n'; + + if (line.Trim() != ".") + { + value += line.Trim(); + } + + values[currentKey] = value; + } + else + { + string[] parts = line.Split(new char[] { ':' }, 2); + currentKey = parts[0].Trim(); + string value = parts[1].Trim(); + + values.Add(currentKey, value); + } + } + } + + return values; + } + } +} diff --git a/DebUOS/Packaging.Targets/Deb/DebPackage.cs b/DebUOS/Packaging.Targets/Deb/DebPackage.cs new file mode 100644 index 0000000..89e4538 --- /dev/null +++ b/DebUOS/Packaging.Targets/Deb/DebPackage.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; + +namespace Packaging.Targets.Deb +{ + /// + /// Represents a Debian installer package. + /// + internal class DebPackage + { + /// + /// Gets or sets the Debian installer file format used. + /// + public Version PackageFormatVersion + { + get; + set; + } + + /// + /// Gets or sets the value of the control file. + /// + public Dictionary ControlFile + { + get; + set; + } + + public Dictionary ControlExtras { get; set; } + + public Dictionary Md5Sums { get; set; } + + public string PreInstallScript { get; set; } + + public string PostInstallScript { get; set; } + + public string PreRemoveScript { get; set; } + + public string PostRemoveScript { get; set; } + } +} diff --git a/DebUOS/Packaging.Targets/Deb/DebPackageControlFileData.cs b/DebUOS/Packaging.Targets/Deb/DebPackageControlFileData.cs new file mode 100644 index 0000000..2de2b6e --- /dev/null +++ b/DebUOS/Packaging.Targets/Deb/DebPackageControlFileData.cs @@ -0,0 +1,15 @@ +using Packaging.Targets.IO; + +namespace Packaging.Targets.Deb +{ + /// + /// Needed for entries we don't particularry care about + /// during package generation, such as `shlibs` + /// + public class DebPackageControlFileData + { + public LinuxFileMode Mode { get; set; } + + public string Contents { get; set; } + } +} \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/Deb/DebPackageCreator.cs b/DebUOS/Packaging.Targets/Deb/DebPackageCreator.cs new file mode 100644 index 0000000..2ac3d31 --- /dev/null +++ b/DebUOS/Packaging.Targets/Deb/DebPackageCreator.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using Packaging.Targets.IO; + +namespace Packaging.Targets.Deb +{ + internal class DebPackageCreator + { + private const LinuxFileMode ArFileMode = LinuxFileMode.S_IRUSR | LinuxFileMode.S_IWUSR | LinuxFileMode.S_IRGRP | + LinuxFileMode.S_IROTH | LinuxFileMode.S_IFREG; + + public static DebPackage BuildDebPackage( + List archiveEntries, + string name, + string description, + string maintainer, + string version, + string arch, + bool createUser, + string userName, + bool installService, + string serviceName, + string prefix, + string section, + string priority, + string homepage, + string preInstallScript, + string postInstallScript, + string preRemoveScript, + string postRemoveScript, + IEnumerable additionalDependencies, + IEnumerable recommends, + Action additionalMetadata) + { + var pkg = new DebPackage + { + Md5Sums = archiveEntries.Where(e => e.Md5Hash != null) + .ToDictionary( + e => e.TargetPath.TrimStart('.', '/'), + e => BitConverter.ToString(e.Md5Hash).ToLower().Replace("-", string.Empty)), + PackageFormatVersion = new Version(2, 0), + ControlFile = new Dictionary + { + ["Package"] = name, + ["Version"] = version, + ["Architecture"] = arch, + ["Maintainer"] = maintainer, + ["Description"] = description, + ["Installed-Size"] = (archiveEntries.Sum(e => e.FileSize) / 1024).ToString() + } + }; + + if (!string.IsNullOrEmpty(section)) + { + pkg.ControlFile["Section"] = section; + } + + if (!string.IsNullOrEmpty(priority)) + { + pkg.ControlFile["Priority"] = priority; + } + + if (!string.IsNullOrEmpty(homepage)) + { + pkg.ControlFile["Homepage"] = homepage; + } + + if (createUser) + { + // Add the user and group, under which the service runs. + // These users are never removed because UIDs are re-used on Linux. + pkg.PreInstallScript += $"/usr/sbin/groupadd -r {userName} 2>/dev/null || :\n" + + $"/usr/sbin/useradd -g {userName} -s /sbin/nologin -r {userName} 2>/dev/null || :\n"; + } + + if (installService) + { + // Install and activate the service. + pkg.PostInstallScript += $"systemctl daemon-reload\n"; + pkg.PostInstallScript += $"systemctl enable --now {serviceName}.service\n"; + pkg.PreRemoveScript += $"systemctl --no-reload disable --now {serviceName}.service\n"; + } + + // Remove all directories marked as such (these are usually directories which contain temporary files) + foreach (var entryToRemove in archiveEntries.Where(e => e.RemoveOnUninstall)) + { + pkg.PostRemoveScript += $"/bin/rm -rf {entryToRemove.TargetPath}\n"; + } + + if (!string.IsNullOrEmpty(preInstallScript)) + { + pkg.PreInstallScript += preInstallScript; + + if (!preInstallScript.EndsWith("\n")) + { + pkg.PreInstallScript += "\n"; + } + } + + if (!string.IsNullOrEmpty(postInstallScript)) + { + pkg.PostInstallScript += postInstallScript; + + if (!postInstallScript.EndsWith("\n")) + { + pkg.PostInstallScript += "\n"; + } + } + + if (!string.IsNullOrEmpty(preRemoveScript)) + { + pkg.PreRemoveScript += preRemoveScript; + + if (!preRemoveScript.EndsWith("\n")) + { + pkg.PreRemoveScript += "\n"; + } + } + + if (!string.IsNullOrEmpty(postRemoveScript)) + { + pkg.PostRemoveScript += postRemoveScript; + + if (!postRemoveScript.EndsWith("\n")) + { + pkg.PostRemoveScript += "\n"; + } + } + + if (additionalDependencies != null && additionalDependencies.Any()) + { + pkg.ControlFile["Depends"] = string.Join(", ", additionalDependencies); + } + + if (recommends != null && recommends.Any()) + { + pkg.ControlFile["Recommends"] = string.Join(", ", recommends); + } + + additionalMetadata?.Invoke(pkg); + + return pkg; + } + + public static void WriteDebPackage( + List archiveEntries, + Stream tarXzStream, + Stream targetStream, + DebPackage pkg) + { + ArFileCreator.WriteMagic(targetStream); + ArFileCreator.WriteEntry(targetStream, "debian-binary", ArFileMode, pkg.PackageFormatVersion + "\n"); + WriteControl(targetStream, pkg, archiveEntries); + ArFileCreator.WriteEntry(targetStream, "data.tar.xz", ArFileMode, tarXzStream); + } + + private static void WriteControl(Stream targetStream, DebPackage pkg, List entries) + { + var controlTar = new MemoryStream(); + WriteControlEntry(controlTar, "./"); + WriteControlEntry( + controlTar, + "./control", + string.Join("\n", pkg.ControlFile + .OrderByDescending(x => x.Key == "Package").ThenBy(x => x.Key) + .Select(x => $"{x.Key}: {x.Value}")) + "\n"); + + WriteControlEntry( + controlTar, + "./md5sums", + string.Join("\n", pkg.Md5Sums.Select(x => $"{x.Value} {x.Key}")) + "\n"); + + var execMode = LinuxFileMode.S_IRUSR | LinuxFileMode.S_IWUSR | LinuxFileMode.S_IXUSR | + LinuxFileMode.S_IRGRP | LinuxFileMode.S_IROTH; + + if (!string.IsNullOrWhiteSpace(pkg.PreInstallScript)) + { + WriteControlEntry(controlTar, "./preinst", $"#!/bin/sh\n{pkg.PreInstallScript}\n", execMode); + } + + if (!string.IsNullOrWhiteSpace(pkg.PostInstallScript)) + { + WriteControlEntry(controlTar, "./postinst", $"#!/bin/sh\n{pkg.PostInstallScript}\n", execMode); + } + + if (!string.IsNullOrWhiteSpace(pkg.PreRemoveScript)) + { + WriteControlEntry(controlTar, "./prerm", $"#!/bin/sh\n{pkg.PreRemoveScript}\n", execMode); + } + + if (!string.IsNullOrWhiteSpace(pkg.PostRemoveScript)) + { + WriteControlEntry(controlTar, "./postrm", $"#!/bin/sh\n{pkg.PostRemoveScript}\n", execMode); + } + + var confFiles = entries + .Where(e => e.Mode.HasFlag(LinuxFileMode.S_IFREG) && e.TargetPath.StartsWith("/etc/")) + .Select(e => e.TargetPath).ToList(); + if (confFiles.Any()) + { + WriteControlEntry(controlTar, "./conffiles", string.Join("\n", confFiles) + "\n"); + } + + TarFileCreator.WriteTrailer(controlTar); + controlTar.Seek(0, SeekOrigin.Begin); + + var controlTarGz = new MemoryStream(); + using (var gzStream = new GZipStream(controlTarGz, CompressionMode.Compress, true)) + { + controlTar.CopyTo(gzStream); + } + + controlTarGz.Seek(0, SeekOrigin.Begin); + ArFileCreator.WriteEntry(targetStream, "control.tar.gz", ArFileMode, controlTarGz); + } + + private static void WriteControlEntry(Stream tar, string name, string data = null, LinuxFileMode? fileMode = null) + { + var s = (data != null) ? new MemoryStream(Encoding.UTF8.GetBytes(data)) : new MemoryStream(); + var mode = fileMode ?? LinuxFileMode.S_IRUSR | LinuxFileMode.S_IWUSR | + LinuxFileMode.S_IRGRP | LinuxFileMode.S_IROTH; + mode |= data == null + ? LinuxFileMode.S_IFDIR | LinuxFileMode.S_IXUSR | LinuxFileMode.S_IXGRP | LinuxFileMode.S_IXOTH + : LinuxFileMode.S_IFREG; + var hdr = new TarHeader + { + FileMode = mode, + FileName = name, + FileSize = (uint)s.Length, + GroupName = "root", + UserName = "root", + LastModified = DateTimeOffset.UtcNow, + Magic = "ustar", + TypeFlag = data == null ? TarTypeFlag.DirType : TarTypeFlag.RegType, + }; + TarFileCreator.WriteEntry(tar, hdr, s); + } + } +} diff --git a/DebUOS/Packaging.Targets/Deb/DebPackageReader.cs b/DebUOS/Packaging.Targets/Deb/DebPackageReader.cs new file mode 100644 index 0000000..20a08a3 --- /dev/null +++ b/DebUOS/Packaging.Targets/Deb/DebPackageReader.cs @@ -0,0 +1,157 @@ +using Packaging.Targets.IO; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; + +namespace Packaging.Targets.Deb +{ + /// + /// Reads objects from a . + /// + internal static class DebPackageReader + { + /// + /// Reads a from a . + /// + /// + /// The from which to read the package. + /// + /// + /// A which represents the package. + /// + internal static DebPackage Read(Stream stream) + { + DebPackage package = new DebPackage(); + using (ArFile archive = new ArFile(stream, leaveOpen: true)) + { + while (archive.Read()) + { + if (archive.FileName == "debian-binary") + { + ReadDebianBinary(archive, package); + } + else if (archive.FileName == "control.tar.gz") + { + ReadControlArchive(archive, package); + } + } + } + + return package; + } + + internal static Stream GetPayloadStream(Stream stream) + { + using (ArFile archive = new ArFile(stream, leaveOpen: true)) + { + while (archive.Read()) + { + if (archive.FileName.StartsWith("data.tar.")) + { + var ext = Path.GetExtension(archive.FileName); + if (ext == ".gz") + { + return new GZipDecompressor(archive.Open(), false); + } + + if (ext == ".xz") + { + // For some reason it complains about corrupted data when we try to read using smaller chunks + var payload = new MemoryStream(); + using (var xz = new XZInputStream(archive.Open())) + { + xz.CopyTo(payload); + payload.Seek(0, SeekOrigin.Begin); + return payload; + } + } + + throw new InvalidDataException("Don't know how to decompress " + archive.FileName); + } + } + + throw new InvalidDataException("data.tar.?? not found"); + } + } + + /// + /// Reads and parses the debian-binary file in the Debian archive. + /// + /// + /// The archive to update with the data read from the debian-binary file. + /// + /// + /// The package to update. + /// + private static void ReadDebianBinary(ArFile archive, DebPackage package) + { + using (Stream stream = archive.Open()) + using (StreamReader reader = new StreamReader(stream)) + { + var content = reader.ReadToEnd(); + package.PackageFormatVersion = new Version(content); + } + } + + private static void ReadControlArchive(ArFile archive, DebPackage package) + { + package.ControlExtras = new Dictionary(); + package.Md5Sums = new Dictionary(); + using (Stream stream = archive.Open()) + using (GZipDecompressor decompressedStream = new GZipDecompressor(stream, leaveOpen: true)) + using (TarFile tarFile = new TarFile(decompressedStream, leaveOpen: true)) + { + while (tarFile.Read()) + { + switch (tarFile.FileName) + { + case "./control": + using (Stream controlFile = tarFile.Open()) + { + package.ControlFile = ControlFileParser.Read(controlFile); + } + + break; + case "./md5sums": + using (var sums = new StreamReader(tarFile.Open())) + { + string line; + while ((line = sums.ReadLine()) != null) + { + var s = line.Split(new[] { " " }, 2, StringSplitOptions.None); + package.Md5Sums[s[1]] = s[0]; + } + } + + break; + case "./preinst": + package.PreInstallScript = tarFile.ReadAsUtf8String(); + break; + case "./postinst": + package.PostInstallScript = tarFile.ReadAsUtf8String(); + break; + case "./prerm": + package.PreRemoveScript = tarFile.ReadAsUtf8String(); + break; + case "./postrm": + package.PostRemoveScript = tarFile.ReadAsUtf8String(); + break; + + case "./": + tarFile.Skip(); + break; + default: + package.ControlExtras[tarFile.FileName] = new DebPackageControlFileData + { + Mode = tarFile.FileHeader.FileMode, + Contents = tarFile.ReadAsUtf8String() + }; + break; + } + } + } + } + } +} diff --git a/DebUOS/Packaging.Targets/DebTask.cs b/DebUOS/Packaging.Targets/DebTask.cs new file mode 100644 index 0000000..586873e --- /dev/null +++ b/DebUOS/Packaging.Targets/DebTask.cs @@ -0,0 +1,363 @@ +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); + } + } +} \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/IO/ArFile.cs b/DebUOS/Packaging.Targets/IO/ArFile.cs new file mode 100644 index 0000000..2b91fe6 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/ArFile.cs @@ -0,0 +1,94 @@ +using System; +using System.IO; +using System.Text; + +namespace Packaging.Targets.IO +{ + /// + /// Represents an ar archive, the format used by Debian installer packages. + /// + /// + public class ArFile : ArchiveFile + { + /// + /// The magic used to identify ar files. + /// + private const string Magic = "!\n"; + + /// + /// Tracks whether the magic has been read or not. + /// + private bool magicRead; + + /// + /// The header of the current entry. + /// + private ArHeader entryHeader; + + /// + /// Initializes a new instance of the class. + /// + /// + /// A which represents the CPIO data. + /// + /// + /// to leave the underlying open when this + /// is disposed of; otherwise, . + /// + public ArFile(Stream stream, bool leaveOpen) + : base(stream, leaveOpen) + { + } + + /// + public override bool Read() + { + this.EnsureMagicRead(); + + if (this.EntryStream != null) + { + this.EntryStream.Dispose(); + } + + this.Align(2); + + if (this.Stream.Position == this.Stream.Length) + { + return false; + } + + this.entryHeader = this.Stream.ReadStruct(); + this.FileHeader = this.entryHeader; + this.FileName = this.entryHeader.FileName; + + if (this.entryHeader.EndChar != "`\n") + { + throw new InvalidDataException("The magic for the file entry is invalid"); + } + + this.EntryStream = new SubStream(this.Stream, this.Stream.Position, this.entryHeader.FileSize, leaveParentOpen: true); + + return true; + } + + /// + /// Reads the magic if it has not been read previously. + /// + protected void EnsureMagicRead() + { + if (!this.magicRead) + { + byte[] buffer = new byte[Magic.Length]; + this.Stream.Read(buffer, 0, buffer.Length); + var magic = Encoding.ASCII.GetString(buffer); + + if (!string.Equals(magic, Magic, StringComparison.Ordinal)) + { + throw new InvalidDataException("The .ar file did not start with the expected magic"); + } + + this.magicRead = true; + } + } + } +} diff --git a/DebUOS/Packaging.Targets/IO/ArFileCreator.cs b/DebUOS/Packaging.Targets/IO/ArFileCreator.cs new file mode 100644 index 0000000..e41e5de --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/ArFileCreator.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; +using System.Text; + +namespace Packaging.Targets.IO +{ + public static class ArFileCreator + { + public static void WriteMagic(Stream output) + { + var wr = new StreamWriter(output); + wr.Write("!\n"); + wr.Flush(); + } + + public static void WriteEntry(Stream output, string name, LinuxFileMode mode, string data) + => WriteEntry(output, name, mode, new MemoryStream(Encoding.UTF8.GetBytes(data))); + + public static void WriteEntry(Stream output, string name, LinuxFileMode mode, Stream data) + { + var hdr = new ArHeader + { + EndChar = "`\n", + FileMode = mode, + FileName = name, + FileSize = (uint)data.Length, + GroupId = 0, + OwnerId = 0, + LastModified = DateTimeOffset.UtcNow + }; + WriteEntry(output, hdr, data); + } + + public static void WriteEntry(Stream output, ArHeader header, Stream data) + { + output.WriteStruct(header); + data.CopyTo(output); + if (output.Position % 2 != 0) + { + output.WriteByte(0); + } + } + } +} \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/IO/ArHeader.cs b/DebUOS/Packaging.Targets/IO/ArHeader.cs new file mode 100644 index 0000000..7e09a6f --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/ArHeader.cs @@ -0,0 +1,141 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Packaging.Targets.IO +{ + /// + /// The header of an entry in an . + /// + public struct ArHeader : IArchiveHeader + { + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 16)] + private byte[] fileName; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 12)] + private byte[] lastModified; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 6)] + private byte[] ownerId; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 6)] + private byte[] groupId; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] + private byte[] fileMode; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 10)] + private byte[] fileSize; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 2)] + private byte[] endChar; + + /// + /// Gets or sets the name of the current file. + /// + public string FileName + { + get => this.GetString(this.fileName, 16).Trim(); + set => this.fileName = this.CreateString(value, 16); + } + + /// + /// Gets or sets the date at which the current file was last modified. + /// + public DateTimeOffset LastModified + { + get => DateTimeOffset.FromUnixTimeSeconds(int.Parse(this.GetString(this.lastModified, 12).Trim())); + set => this.lastModified = this.CreateString(value.ToUnixTimeSeconds().ToString(), 12); + } + + /// + /// Gets or sets the user ID of the owner of the file. + /// + public uint OwnerId + { + get => this.ReadUInt(this.ownerId); + set => this.ownerId = this.CreateString(value.ToString(), 6); + } + + /// + /// Gets or sets group ID of the owner of the file. + /// + public uint GroupId + { + get => this.ReadUInt(this.groupId); + set => this.groupId = this.CreateString(value.ToString(), 6); + } + + /// + public LinuxFileMode FileMode + { + get => (LinuxFileMode)Convert.ToUInt32(this.GetString(this.fileMode, 8).Trim(), 8); + set => this.fileMode = this.CreateString(Convert.ToString((uint)value, 8), 8); + } + + /// + public uint FileSize + { + get => this.ReadUInt(this.fileSize); + set => this.fileSize = this.CreateString(value.ToString(), 10); + } + + /// + /// Gets or sets the value of the endchar field. This field is used as a checksum. + /// + public string EndChar + { + get => this.GetString(this.endChar, 2); + set => this.endChar = this.CreateString(value, 2); + } + + /// + public override string ToString() + { + return this.FileName; + } + + private string GetString(byte[] data, int maxLen) + { + int len; + for (len = 0; len < maxLen; len++) + { + if (data[len] == 0) + { + break; + } + } + + if (len == 0) + { + return null; + } + + return Encoding.UTF8.GetString(data, 0, len); + } + + private uint ReadUInt(byte[] data) => Convert.ToUInt32(this.GetString(data, data.Length).Trim()); + + private byte[] CreateString(string s, int len) + { + var target = new byte[len]; + if (s == null) + { + return target; + } + + var buffer = Encoding.UTF8.GetBytes(s); + if (buffer.Length > len) + { + throw new Exception($"String {s} exceeds the limit of {len}"); + } + + for (var c = 0; c < len; c++) + { + target[c] = (c < buffer.Length) ? buffer[c] : (byte)0x20; + } + + return target; + } + } +} diff --git a/DebUOS/Packaging.Targets/IO/ArchiveEntry.cs b/DebUOS/Packaging.Targets/IO/ArchiveEntry.cs new file mode 100644 index 0000000..2704259 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/ArchiveEntry.cs @@ -0,0 +1,132 @@ +using System; + +namespace Packaging.Targets.IO +{ + /// + /// Represents an entry in a Unix archive (usually a CPIO or tar archive). + /// + public class ArchiveEntry + { + /// + /// Gets or sets the full path of the file or directory on the target file system. + /// + public string TargetPath + { get; set; } + + /// + /// Gets or sets the name of the owner of the file or directory. + /// + public string Owner + { get; set; } + + /// + /// Gets or sets the name of the group of the file or directory. + /// + public string Group + { get; set; } + + /// + /// Gets or sets the file mode of the file. + /// + public LinuxFileMode Mode + { get; set; } + + /// + /// Gets or sets the full path to the file on the source operating system. + /// + public string SourceFilename + { get; set; } + + /// + /// Gets or sets the total file size, in bytes. + /// + public uint FileSize + { get; set; } + + /// + /// Gets or sets the date at which the file was last modified. + /// + public DateTimeOffset Modified + { get; set; } + + /// + /// Gets or sets the SHA256 of the file contents. + /// + public byte[] Sha256 + { get; set; } + + /// + /// Gets or sets the MD5 of the file contents. + /// + public byte[] Md5Hash + { get; set; } + + /// + /// Gets or sets the file type (executable or not). + /// + public ArchiveEntryType Type + { get; set; } + + /// + /// Gets or sets, if the file is a link, the link target. + /// + public string LinkTo + { get; set; } + + /// + /// Gets or sets the inode number for the file. + /// + public uint Inode + { get; set; } + + /// + /// Gets or sets a value indicating whether the file only contains ASCII characters. + /// + public bool IsAscii + { get; set; } + + /// + /// Gets or sets a value indicating whether this entry (and any child entries) is forcefully + /// removed when the application is uninstalled. + /// + public bool RemoveOnUninstall + { get; set; } + + /// + /// Gets the file name, including a final slash if the file is a directory. + /// Used when sorting entries to make sure a directory entry immediately preceeds + /// all of its children. + /// + public string TargetPathWithFinalSlash + { + get + { + if (this.Mode.HasFlag(LinuxFileMode.S_IFDIR) && !this.TargetPath.EndsWith("/")) + { + return this.TargetPath + "/"; + } + else + { + return this.TargetPath; + } + } + } + + /// + /// Gets the file name, excluding a final slash even if the file is a directory. + /// + public string TargetPathWithoutFinalSlash + { + get + { + return this.TargetPath?.TrimEnd('/'); + } + } + + /// + public override string ToString() + { + return this.TargetPath; + } + } +} diff --git a/DebUOS/Packaging.Targets/IO/ArchiveEntryType.cs b/DebUOS/Packaging.Targets/IO/ArchiveEntryType.cs new file mode 100644 index 0000000..4010595 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/ArchiveEntryType.cs @@ -0,0 +1,23 @@ +namespace Packaging.Targets.IO +{ + /// + /// Represents the type of entry in an archive. + /// + public enum ArchiveEntryType + { + /// + /// The file as no special type. + /// + None = 0, + + /// + /// The file is a 32-bit executable. + /// + Executable32 = 1, + + /// + /// The file is a 64-bit executable. + /// + Executable64 = 2 + } +} diff --git a/DebUOS/Packaging.Targets/IO/ArchiveFile.cs b/DebUOS/Packaging.Targets/IO/ArchiveFile.cs new file mode 100644 index 0000000..6245b07 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/ArchiveFile.cs @@ -0,0 +1,175 @@ +using System; +using System.IO; + +namespace Packaging.Targets.IO +{ + /// + /// Base class for archive files, such as CPIO files or ar files. + /// + public abstract class ArchiveFile : IDisposable + { + /// + /// The around which this wraps. + /// + private readonly Stream stream; + + /// + /// Indicates whether should be disposed of when this is disposed of. + /// + private readonly bool leaveOpen; + + /// + /// Tracks whether this has been disposed of or not. + /// + private bool disposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// A which represents the archive data. + /// + /// + /// to leave the underlying open when this + /// is disposed of; otherwise, . + /// + public ArchiveFile(Stream stream, bool leaveOpen) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + this.stream = stream; + this.leaveOpen = leaveOpen; + } + + /// + /// Gets or sets a which represents the current entry. + /// + public SubStream EntryStream + { + get; + protected set; + } + + /// + /// Gets or sets the name of the current entry. + /// + public string FileName + { + get; + protected set; + } + + /// + /// Gets or sets the header for the current file. + /// + public IArchiveHeader FileHeader + { + get; + protected set; + } + + /// + /// Gets the which underpins this . + /// + protected Stream Stream + { + get + { + this.EnsureNotDisposed(); + return this.stream; + } + } + + public static int PaddingSize(int multiple, int value) + { + if (value % multiple == 0) + { + return 0; + } + else + { + return multiple - value % multiple; + } + } + + /// + /// Returns a which represents the content of the current entry in the . + /// + /// + /// A which represents the content of the current entry in the . + /// + public Stream Open() + { + return this.EntryStream; + } + + /// + public void Dispose() + { + if (!this.leaveOpen) + { + this.Stream.Dispose(); + } + + this.disposed = true; + } + + /// + /// Reads the next entry in the archive. + /// + /// + /// if an entry is present, if no more entries + /// could be found in the archive. + /// + public abstract bool Read(); + + /// + /// Skips the current entry, without reading the entry data. + /// + public void Skip() + { + byte[] buffer = new byte[60 * 1024]; + + while (this.EntryStream.Read(buffer, 0, buffer.Length) > 0) + { + // Keep reading until we're at the end of the stream. + } + } + + /// + /// Throws a if has been previously called. + /// + protected void EnsureNotDisposed() + { + if (this.disposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + } + + /// + /// Aligns the current position. + /// + /// + /// The value to which to align. + /// + protected void Align(int alignmentBase) + { + var currentIndex = + (int)(this.EntryStream != null ? (this.EntryStream.Offset + this.EntryStream.Length) : this.Stream.Position); + + if (this.Stream.CanSeek) + { + this.Stream.Seek(currentIndex + PaddingSize(alignmentBase, currentIndex), SeekOrigin.Begin); + } + else + { + byte[] buffer = new byte[PaddingSize(alignmentBase, currentIndex)]; + this.Stream.Read(buffer, 0, buffer.Length); + } + } + } +} diff --git a/DebUOS/Packaging.Targets/IO/ConcatStream.cs b/DebUOS/Packaging.Targets/IO/ConcatStream.cs new file mode 100644 index 0000000..77eebee --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/ConcatStream.cs @@ -0,0 +1,271 @@ +// Copyright (c) 2008-2011, Kenneth Bell +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; + +namespace DiscUtils.Internal +{ + /// + /// The concatenation of multiple streams (read-only, for now). + /// + internal class ConcatStream : Stream + { + private readonly bool canWrite; + private readonly bool leaveOpen; + + private long position; + private Stream[] streams; + + public ConcatStream(params Stream[] streams) + : this(false, streams) + { + } + + public ConcatStream(bool leaveOpen, params Stream[] streams) + { + this.streams = streams; + this.leaveOpen = leaveOpen; + + // Only allow writes if all streams can be written + this.canWrite = true; + foreach (Stream stream in streams) + { + if (!stream.CanWrite) + { + this.canWrite = false; + } + } + } + + public override bool CanRead + { + get + { + this.CheckDisposed(); + return true; + } + } + + public override bool CanSeek + { + get + { + this.CheckDisposed(); + return true; + } + } + + public override bool CanWrite + { + get + { + this.CheckDisposed(); + return this.canWrite; + } + } + + public override long Length + { + get + { + this.CheckDisposed(); + long length = 0; + for (int i = 0; i < this.streams.Length; ++i) + { + length += this.streams[i].Length; + } + + return length; + } + } + + public override long Position + { + get + { + this.CheckDisposed(); + return this.position; + } + + set + { + this.CheckDisposed(); + this.position = value; + } + } + + public override void Flush() + { + this.CheckDisposed(); + for (int i = 0; i < this.streams.Length; ++i) + { + this.streams[i].Flush(); + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + this.CheckDisposed(); + + int totalRead = 0; + int numRead = 0; + + do + { + long activeStreamStartPos; + int activeStream = this.GetActiveStream(out activeStreamStartPos); + + this.streams[activeStream].Position = this.position - activeStreamStartPos; + + numRead = this.streams[activeStream].Read(buffer, offset + totalRead, count - totalRead); + + totalRead += numRead; + this.position += numRead; + } + while (numRead != 0); + + return totalRead; + } + + public override long Seek(long offset, SeekOrigin origin) + { + this.CheckDisposed(); + + long effectiveOffset = offset; + if (origin == SeekOrigin.Current) + { + effectiveOffset += this.position; + } + else if (origin == SeekOrigin.End) + { + effectiveOffset += this.Length; + } + + if (effectiveOffset < 0) + { + throw new IOException("Attempt to move before beginning of disk"); + } + + this.Position = effectiveOffset; + return this.Position; + } + + public override void SetLength(long value) + { + this.CheckDisposed(); + + long lastStreamOffset; + int lastStream = this.GetStream(this.Length, out lastStreamOffset); + if (value < lastStreamOffset) + { + throw new IOException($"Unable to reduce stream length to less than {lastStreamOffset}"); + } + + this.streams[lastStream].SetLength(value - lastStreamOffset); + } + + public override void Write(byte[] buffer, int offset, int count) + { + this.CheckDisposed(); + + int totalWritten = 0; + while (totalWritten != count) + { + // Offset of the stream = streamOffset + long streamOffset; + int streamIdx = this.GetActiveStream(out streamOffset); + + // Offset within the stream = streamPos + long streamPos = this.position - streamOffset; + this.streams[streamIdx].Position = streamPos; + + // Write (limited to the stream's length), except for final stream - that may be + // extendable + int numToWrite; + if (streamIdx == this.streams.Length - 1) + { + numToWrite = count - totalWritten; + } + else + { + numToWrite = (int)Math.Min(count - totalWritten, this.streams[streamIdx].Length - streamPos); + } + + this.streams[streamIdx].Write(buffer, offset + totalWritten, numToWrite); + + totalWritten += numToWrite; + this.position += numToWrite; + } + } + + protected override void Dispose(bool disposing) + { + try + { + if (disposing && this.streams != null) + { + if (!this.leaveOpen) + { + foreach (Stream stream in this.streams) + { + stream.Dispose(); + } + } + + this.streams = null; + } + } + finally + { + base.Dispose(disposing); + } + } + + private int GetActiveStream(out long startPos) + { + return this.GetStream(this.position, out startPos); + } + + private int GetStream(long targetPos, out long streamStartPos) + { + // Find the stream that _position is within + streamStartPos = 0; + int focusStream = 0; + while (focusStream < this.streams.Length - 1 && streamStartPos + this.streams[focusStream].Length <= targetPos) + { + streamStartPos += this.streams[focusStream].Length; + focusStream++; + } + + return focusStream; + } + + private void CheckDisposed() + { + if (this.streams == null) + { + throw new ObjectDisposedException(nameof(ConcatStream)); + } + } + } +} \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/IO/CpioFile.cs b/DebUOS/Packaging.Targets/IO/CpioFile.cs new file mode 100644 index 0000000..3d25007 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/CpioFile.cs @@ -0,0 +1,187 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace Packaging.Targets.IO +{ + /// + /// The cpio archive format collects any number of files, directories, and + /// other file system objects(symbolic links, device nodes, etc.) into a + /// single stream of bytes. + /// + /// + /// Each file system object in a cpio archive comprises a header record with + /// basic numeric metadata followed by the full pathname of the entry and the + /// file data.The header record stores a series of integer values that generally + /// follow the fields in struct stat. (See stat(2) for details.) The + /// variants differ primarily in how they store those integers(binary, + /// octal, or hexadecimal). The header is followed by the pathname of the + /// entry(the length of the pathname is stored in the header) and any file + /// data.The end of the archive is indicated by a special record with the + /// pathname TRAILER!!!. + /// + /// + public class CpioFile : ArchiveFile, IDisposable + { + /// + /// The entry which is currently available. + /// + private CpioHeader entryHeader; + + /// + /// The position at which the data for the current entry starts. + /// + private long entryDataOffset; + + /// + /// The length of the data for the current entry. + /// + private long entryDataLength; + + /// + /// Initializes a new instance of the class. + /// + /// + /// A which represents the CPIO data. + /// + /// + /// to leave the underlying open when this + /// is disposed of; otherwise, . + /// + public CpioFile(Stream stream, bool leaveOpen) + : base(stream, leaveOpen) + { + } + + /// + /// Gets the header of the current entry. + /// + public CpioHeader EntryHeader + { + get { return this.entryHeader; } + } + + /// + /// Gets a value indicating whether the current entry is a directory. + /// + public bool IsDirectory + { + get { return this.entryDataLength == 0; } + } + + /// + /// Adds an entry to the + /// + /// + /// A with the item metaata. The , + /// and values are overwritten. + /// + /// + /// The file name of the entry. + /// + /// + /// A which contains the file data. + /// + public void Write(CpioHeader header, string name, Stream data) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + byte[] nameBytes = Encoding.UTF8.GetBytes(name); + + // We make sure the magic and size fields have the correct values. All other fields + // are the responsibility of the caller. + header.Signature = "070701"; + header.NameSize = (uint)(nameBytes.Length + 1); + header.FileSize = (uint)data.Length; + + this.Stream.WriteStruct(header); + this.Stream.Write(nameBytes, 0, nameBytes.Length); + this.Stream.WriteByte(0); // Trailing 0 + + // The pathname is followed by NUL bytes so that the total size of the fixed + // header plus pathname is a multiple of four. + var headerSize = Marshal.SizeOf() + (int)header.NameSize; + var paddingSize = PaddingSize(4, headerSize); + + for (int i = 0; i < paddingSize; i++) + { + this.Stream.WriteByte(0); + } + + data.Position = 0; + data.CopyTo(this.Stream); + + // The file data is padded to a multiple of four bytes. + paddingSize = PaddingSize(4, (int)data.Length); + + for (int i = 0; i < paddingSize; i++) + { + this.Stream.WriteByte(0); + } + } + + /// + /// Writes the trailer entry. + /// + public void WriteTrailer() + { + this.Write(CpioHeader.Empty, "TRAILER!!!", new MemoryStream(Array.Empty())); + } + + /// + /// Reads the next entry in the . + /// + /// + /// if more data is available; otherwise, . + /// + public override bool Read() + { + if (this.EntryStream != null) + { + this.EntryStream.Dispose(); + } + + this.Align(4); + + this.entryHeader = this.Stream.ReadStruct(); + this.FileHeader = this.entryHeader; + + if (this.entryHeader.Signature != "070701") + { + throw new InvalidDataException("The magic for the file entry is invalid"); + } + + byte[] nameBytes = new byte[this.entryHeader.NameSize]; + this.Stream.Read(nameBytes, 0, nameBytes.Length); + + this.FileName = Encoding.UTF8.GetString(nameBytes, 0, (int)this.entryHeader.NameSize - 1); + + // The pathname is followed by NUL bytes so that the total size of the fixed + // header plus pathname is a multiple of four. + var headerSize = Marshal.SizeOf() + nameBytes.Length; + var paddingSize = PaddingSize(4, headerSize); + + if (nameBytes.Length < paddingSize) + { + nameBytes = new byte[paddingSize]; + } + + this.Stream.Read(nameBytes, 0, paddingSize); + + this.entryDataOffset = this.Stream.Position; + this.entryDataLength = this.entryHeader.FileSize; + this.EntryStream = new SubStream(this.Stream, this.entryDataOffset, this.entryDataLength, leaveParentOpen: true); + + return this.FileName != "TRAILER!!!"; + } + } +} diff --git a/DebUOS/Packaging.Targets/IO/CpioFileCreator.cs b/DebUOS/Packaging.Targets/IO/CpioFileCreator.cs new file mode 100644 index 0000000..ada0014 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/CpioFileCreator.cs @@ -0,0 +1,181 @@ +using Packaging.Targets.Rpm; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Packaging.Targets.IO +{ + /// + /// Supports generating CPIO files. + /// + public class CpioFileCreator + { + /// + /// The used to fetch file metadata. + /// + private IFileAnalyzer fileAnayzer; + + /// + /// Initializes a new instance of the class. + /// + public CpioFileCreator() + { + this.fileAnayzer = new FileAnalyzer(); + } + + /// + /// Generates a based on a list of + /// values. + /// + /// + /// The values based on which to generate the . + /// + /// + /// The which will hold the . + /// + public void FromArchiveEntries(List archiveEntries, Stream targetStream) + { + using (CpioFile cpioFile = new CpioFile(targetStream, leaveOpen: true)) + { + foreach (var entry in archiveEntries) + { + if (entry.Mode.HasFlag(LinuxFileMode.S_IFDIR)) + { + this.AddDirectory(entry, cpioFile); + } + else if (entry.Mode.HasFlag(LinuxFileMode.S_IFLNK)) + { + this.AddSymlink(entry, cpioFile); + } + else + { + this.AddFile(entry, cpioFile); + } + } + + cpioFile.WriteTrailer(); + } + } + + /// + /// Adds a directory entry to the . + /// + /// + /// The which represents the directory. + /// + /// + /// The to which to add the directory entry. + /// + public void AddDirectory(ArchiveEntry entry, CpioFile cpioFile) + { + // Write out an entry for the current directory + CpioHeader directoryHeader = new CpioHeader() + { + Check = 0, + DevMajor = 1, + DevMinor = 0, + FileSize = 0, + Gid = 0, + Ino = entry.Inode, + FileMode = entry.Mode, + LastModified = entry.Modified, + Nlink = 1, + RDevMajor = 0, + RDevMinor = 0, + Signature = "070701", + Uid = 0, + NameSize = 0 + }; + + var targetPath = entry.TargetPath; + if (!targetPath.StartsWith(".")) + { + targetPath = "." + targetPath; + } + + cpioFile.Write(directoryHeader, targetPath, new MemoryStream(Array.Empty())); + } + + /// + /// Adds a symlink entry to a . + /// + /// + /// The symlink entry to add. + /// + /// + /// The to which to add the entry. + /// + 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))); + } + + /// + /// Adds a file entry to a . + /// + /// + /// The file entry to add. + /// + /// + /// The to which to add the entry. + /// + public void AddFile(ArchiveEntry entry, CpioFile cpioFile) + { + var targetPath = entry.TargetPath; + + if (!targetPath.StartsWith(".")) + { + targetPath = "." + targetPath; + } + + using (Stream fileStream = File.OpenRead(entry.SourceFilename)) + { + 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, fileStream); + } + } + } +} diff --git a/DebUOS/Packaging.Targets/IO/CpioHeader.cs b/DebUOS/Packaging.Targets/IO/CpioHeader.cs new file mode 100644 index 0000000..7c4f3b1 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/CpioHeader.cs @@ -0,0 +1,262 @@ +using System; +using System.Runtime.InteropServices; + +namespace Packaging.Targets.IO +{ + /// + /// Represents the header of an individual entry in a CPIO file, in ASCII format. + /// + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] + public struct CpioHeader : IArchiveHeader + { + /// + /// The integer value octal 070701. This value can be used to determine + /// whether this archive is written with little-endian or big-endian integers, + /// or ASCII. + /// + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 6)] // 0 through 5 + private char[] signature; + + /// + /// The inode number from the disk. These are used by + /// programs that read cpio archives to determine when two entries + /// refer to the same file. Programs that synthesize cpio archives + /// should be careful to set these to distinct values for each entry. + /// + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 6 through 13 + private char[] ino; + + /// + /// The mode specifies both the regular permissions and the file type. + /// + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 14 through 21 + private char[] mode; + + /// + /// The numeric user id of the owner. + /// + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 22 through 29 + private char[] uid; + + /// + /// The numeric group id of the owner. + /// + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 30 through 37 + private char[] gid; + + /// + /// The number of links to this file. Directories always have a + /// value of at least two here. Note that hardlinked files include + /// file data with every copy in the archive. + /// + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 38 through 45 + private char[] nlink; + + /// + /// Modification time of the file, indicated as the number of seconds + /// since the start of the epoch, 00:00:00 UTC January 1, 1970. The + /// four-byte integer is stored with the most-significant 16 bits + /// first followed by the least-significant 16 bits.Each of the two + /// 16 bit values are stored in machine-native byte order. + /// + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 46 through 53 + private char[] mtime; + + /// + /// The size of the file. Note that this archive format is limited + /// to four gigabyte file sizes.See mtime above for a description + /// of the storage of four-byte integers. + /// + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 54 through 61 + private char[] filesize; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 62 through 69 + private char[] devMajor; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 70 through 77 + private char[] devMinor; + + /// + /// For block special and character special entries, this field contains + /// the associated device number. For all other entry types, + /// it should be set to zero by writers and ignored by readers. + /// + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 78 through 85 + private char[] rdevMajor; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 86 through 93 + private char[] rdevMinor; + + /// + /// The number of bytes in the pathname that follows the header. + /// This count includes the trailing NUL byte. + /// + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 94 through 101 + private char[] namesize; + + /// + /// This field is always set to zero by writers and ignored by readers. + /// + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] // 102 through 109 + private char[] check; + + /// + /// Gets an empty entry. + /// + public static CpioHeader Empty + { + get + { + return new CpioHeader() + { + Check = 0, + DevMajor = 0, + DevMinor = 0, + FileSize = 0, + Gid = 0, + Ino = 0, + FileMode = 0, + LastModified = DateTimeOffset.FromUnixTimeSeconds(0), + NameSize = 0, + Nlink = 1, + RDevMajor = 0, + RDevMinor = 0, + Signature = "070701", + Uid = 0 + }; + } + } + + /// + /// Gets or sets the value of the field as a . + /// The integer value octal 070701. This value can be used to determine + /// whether this archive is written with little-endian or big-endian integers, + /// or ASCII. + /// + public string Signature + { + get { return new string(this.signature); } + set { this.signature = value.ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// + public uint Ino + { + get { return Convert.ToUInt32(new string(this.ino), 16); } + set { this.ino = value.ToString("x8").ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// + public LinuxFileMode FileMode + { + get { return (LinuxFileMode)Convert.ToUInt32(new string(this.mode), 16); } + set { this.mode = ((uint)value).ToString("x8").ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// + public uint Uid + { + get { return Convert.ToUInt32(new string(this.uid), 16); } + set { this.uid = value.ToString("x8").ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// The numeric group id of the owner. + /// + public uint Gid + { + get { return Convert.ToUInt32(new string(this.gid), 16); } + set { this.gid = value.ToString("x8").ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// + public uint Nlink + { + get { return Convert.ToUInt32(new string(this.nlink), 16); } + set { this.nlink = value.ToString("x8").ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// + public DateTimeOffset LastModified + { + get { return DateTimeOffset.FromUnixTimeSeconds((long)Convert.ToUInt32(new string(this.mtime), 16)); } + set { this.mtime = value.ToUnixTimeSeconds().ToString("x8").ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// + public uint FileSize + { + get { return Convert.ToUInt32(new string(this.filesize), 16); } + set { this.filesize = value.ToString("x8").ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// + public uint DevMajor + { + get { return Convert.ToUInt32(new string(this.devMajor), 16); } + set { this.devMajor = value.ToString("x8").ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// + public uint DevMinor + { + get { return Convert.ToUInt32(new string(this.devMinor), 16); } + set { this.devMinor = value.ToString("x8").ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// + public uint RDevMajor + { + get { return Convert.ToUInt32(new string(this.rdevMajor), 16); } + set { this.rdevMajor = value.ToString("x8").ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// + public uint RDevMinor + { + get { return Convert.ToUInt32(new string(this.rdevMinor), 16); } + set { this.rdevMinor = value.ToString("x8").ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// + public uint NameSize + { + get { return Convert.ToUInt32(new string(this.namesize), 16); } + set { this.namesize = value.ToString("x8").ToCharArray(); } + } + + /// + /// Gets or sets the value of the field as a . + /// This field is always set to zero by writers and ignored by readers. + /// + public uint Check + { + get { return Convert.ToUInt32(new string(this.check), 16); } + set { this.check = value.ToString("x8").ToCharArray(); } + } + } +} diff --git a/DebUOS/Packaging.Targets/IO/DlOpenFlags.cs b/DebUOS/Packaging.Targets/IO/DlOpenFlags.cs new file mode 100644 index 0000000..c404e7d --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/DlOpenFlags.cs @@ -0,0 +1,70 @@ +namespace Packaging.Targets.IO +{ + /// + /// Specifies how the function loads + /// a dynamic link library. + /// + internal enum DlOpenFlags : int + { + /// + /// Perform lazy binding. Only resolve symbols as the code that references them is executed. + /// If the symbol is never referenced, then it is never resolved. + /// (Lazy binding is only performed for function references; references to variables are always + /// immediately bound when the library is loaded.) + /// + RTLD_LAZY = 0x00001, + + /// + /// If this value is specified, or the environment variable LD_BIND_NOW is set to a nonempty string, + /// all undefined symbols in the library are resolved before returns. + /// If this cannot be done, an error is returned. + /// + RTLD_NOW = 0x00002, + + /// + /// The symbols defined by this library will be made available for symbol resolution of subsequently + /// loaded libraries. + /// + RTLD_GLOBAL = 0x00100, + + /// + /// This is the converse of , and the default if neither flag is specified. + /// Symbols defined in this library are not made available to resolve references in subsequently + /// loaded libraries. + /// + RTLD_LOCAL = 0, + + /// + /// Do not unload the library during . Consequently, the library's + /// static variables are not reinitialized if the library is reloaded with + /// at a later time. + /// + /// + /// This flag is not specified in POSIX.1-2001. + /// + RTLD_NODELETE = 4096, + + /// + /// Don't load the library. This can be used to test if the library is already resident + /// ( returns if it is not, or the library's + /// handle if it is resident). This flag can also be used to promote the flags on a library + /// that is already loaded. For example, a library that was previously loaded with + /// can be reopened with | + /// . + /// + /// + /// This flag is not specified in POSIX.1-2001. + /// + RTLD_NOLOAD = 4, + + /// + /// Place the lookup scope of the symbols in this library ahead of the global scope. + /// This means that a self-contained library will use its own symbols in preference to global + /// symbols with the same name contained in libraries that have already been loaded. + /// + /// + /// This flag is not specified in POSIX.1-2001. + /// + RTLD_DEEPBIND = 8 + } +} diff --git a/DebUOS/Packaging.Targets/IO/Extensions.cs b/DebUOS/Packaging.Targets/IO/Extensions.cs new file mode 100644 index 0000000..aeb94a5 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/Extensions.cs @@ -0,0 +1,21 @@ +using System.IO; +using System.Text; + +namespace Packaging.Targets.IO +{ + internal static class Extensions + { + public static string ReadAsUtf8String(this TarFile file) + { + // Use Encoding.UTF8 on a byte array instead of a StreamReader to make sure + // we stop reading when we encounter a null (\0) character. + using (var stream = file.Open()) + { + byte[] data = new byte[stream.Length]; + stream.Read(data, 0, data.Length); + + return Encoding.UTF8.GetString(data); + } + } + } +} \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/IO/GZipDecompressor.cs b/DebUOS/Packaging.Targets/IO/GZipDecompressor.cs new file mode 100644 index 0000000..0b3be9f --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/GZipDecompressor.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; +using System.IO.Compression; + +namespace Packaging.Targets.IO +{ + /// + /// Provides read-only access do a decompressed , and keeps track of the current position. + /// + internal class GZipDecompressor : GZipStream + { + private long position = 0; + + public GZipDecompressor(Stream stream, bool leaveOpen) + : base(stream, CompressionMode.Decompress, leaveOpen) + { + } + + /// + public override long Position + { + get { return this.position; } + set { throw new NotSupportedException(); } + } + + /// + public override int Read(byte[] array, int offset, int count) + { + var read = base.Read(array, offset, count); + this.position += read; + return read; + } + } +} diff --git a/DebUOS/Packaging.Targets/IO/IArchiveHeader.cs b/DebUOS/Packaging.Targets/IO/IArchiveHeader.cs new file mode 100644 index 0000000..729d8f7 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/IArchiveHeader.cs @@ -0,0 +1,25 @@ +using System; + +namespace Packaging.Targets.IO +{ + /// + /// A common interface for the header of a file entry in an archive file. + /// + public interface IArchiveHeader + { + /// + /// Gets the file mode. + /// + LinuxFileMode FileMode { get; } + + /// + /// Gets the date at which the file was last modified. + /// + DateTimeOffset LastModified { get; } + + /// + /// Gets the size of the file. + /// + uint FileSize { get; } + } +} diff --git a/DebUOS/Packaging.Targets/IO/LinuxFileMode.cs b/DebUOS/Packaging.Targets/IO/LinuxFileMode.cs new file mode 100644 index 0000000..3e3144c --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/LinuxFileMode.cs @@ -0,0 +1,122 @@ +using System; + +namespace Packaging.Targets.IO +{ + /// + /// Represents the Unix-style file permission flags. Taken from <sys/stat.h>. + /// + /// + [Flags] + public enum LinuxFileMode : ushort + { + /// + /// No file mode has been specified. + /// + None = 0, + + /// + /// Set user ID on execution + /// + S_ISUID = 0x0800, + + /// + /// Set group ID on execution + /// + S_ISGID = 0x0400, + + /// + /// Save swapped text after use (sticky). + /// + S_ISVTX = 0x0200, + + /// + /// Read by owner + /// + S_IRUSR = 0x0100, + + /// + /// Write by owner + /// + S_IWUSR = 0x0080, + + /// + /// Execute by owner + /// + S_IXUSR = 0x0040, + + /// + /// Read by group + /// + S_IRGRP = 0x0020, + + /// + /// Write by group + /// + S_IWGRP = 0x0010, + + /// + /// Execute by group + /// + S_IXGRP = 0x0008, + + /// + /// Read by other + /// + S_IROTH = 0x0004, + + /// + /// Write by other + /// + S_IWOTH = 0x0002, + + /// + /// Execute by other + /// + S_IXOTH = 0x0001, + + /// + /// The file is a named pipe (fifo). + /// + S_IFIFO = 0x1000, // 010000 in octal + + /// + /// The file is a character special device. + /// + S_IFCHR = 0x2000, // 0020000 in octal + + /// + /// The file is a directory. + /// + S_IFDIR = 0x4000, // 0040000 in octal + + /// + /// The file is a block special device. + /// + S_IFBLK = 0x6000, // 0060000 in octal + + /// + /// The file is a regular file. + /// + S_IFREG = 0x8000, // 0100000 in octal + + /// + /// The file is a symbolic link. + /// + S_IFLNK = 0xA000, // 0120000 in octal + + /// + /// The file is a Unix socket. + /// + S_IFSOCK = 0xC000, // 0140000 in octal + + /// + /// A flag to get all permissions applied to this file. + /// + PermissionsMask = 0x0FFF, + + /// + /// A flag to get the file type. + /// + FileTypeMask = 0xF000, + } +} diff --git a/DebUOS/Packaging.Targets/IO/LzmaAction.cs b/DebUOS/Packaging.Targets/IO/LzmaAction.cs new file mode 100644 index 0000000..51d7a68 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/LzmaAction.cs @@ -0,0 +1,136 @@ +namespace Packaging.Targets.IO +{ + /// + /// The `action' argument for lzma_code() + /// + /// + /// After the first use of , , , + /// or , the same `action' must is used until returns + /// . Also, the amount of input (that is, strm->avail_in) must + /// not be modified by the application until returns + /// . Changing the `action' or modifying the amount of input + /// will make return . + /// + internal enum LzmaAction + { + /// + /// Continue coding + /// + /// + /// + /// Encoder: Encode as much input as possible. Some internal + /// buffering will probably be done(depends on the filter + /// chain in use), which causes latency: the input used won't + /// usually be decodeable from the output of the same + /// call. + /// + /// + /// Decoder: Decode as much input as possible and produce as + /// much output as possible. + /// + /// + Run = 0, + + /// + /// Make all the input available at output + /// + /// + /// + /// Normally the encoder introduces some latency. + /// forces all the buffered data to be + /// available at output without resetting the internal + /// + /// + /// state of the encoder.This way it is possible to use + /// compressed stream for example for communication over + /// network. + /// + /// + /// Only some filters support . Trying to use + /// with filters that don't support it will + /// make return . For example, + /// LZMA1 doesn't support but LZMA2 does. + /// + /// + /// Using very often can dramatically reduce + /// the compression ratio. With some filters (for example, + /// LZMA2), fine-tuning the compression options may help + /// mitigate this problem significantly (for example, + /// match finder with LZMA2). + /// + /// + /// Decoders don't support . + /// + /// + SyncFlush = 1, + + /// + /// Finish encoding of the current Block + /// + /// + /// + /// All the input data going to the current Block must have + /// been given to the encoder (the last bytes can still be + /// pending next_in). Call with + /// until it returns . Then continue normally + /// with or finish the Stream with . + /// + /// + /// This action is currently supported only by Stream encoder + /// and easy encoder (which uses Stream encoder). If there is + /// no unfinished Block, no empty Block is created. + /// + /// + FullFlush = 2, + + /// + /// Finish the coding operation + /// + /// + /// + /// All the input data must have been given to the encoder + /// (the last bytes can still be pending in next_in). + /// Call with until it returns + /// . Once has been used, + /// the amount of input must no longer be changed by + /// the application. + /// + /// + /// When decoding, using is optional unless the + /// flag was used when the decoder was + /// initialized. When was not used, the only + /// effect of is that the amount of input must not + /// be changed just like in the encoder. + /// + /// + Finish = 3, + + /// + /// Finish encoding of the current Block + /// + /// + /// + /// This is like except that this doesn't + /// necessarily wait until all the input has been made + /// available via the output buffer. That is, + /// might return as soon as all the input + /// has been consumed (avail_in == 0). + /// + /// + /// is useful with a threaded encoder if + /// one wants to split the .xz Stream into Blocks at specific + /// offsets but doesn't care if the output isn't flushed + /// immediately. Using allows keeping + /// the threads busy while would make + /// wait until all the threads have finished + /// until more data could be passed to the encoder. + /// + /// + /// With a initialized with the single-threaded + /// lzma_stream_encoder() or lzma_easy_encoder(), + /// is an alias for . + /// + /// + FullBarrier = 4 + } +} diff --git a/DebUOS/Packaging.Targets/IO/LzmaCheck.cs b/DebUOS/Packaging.Targets/IO/LzmaCheck.cs new file mode 100644 index 0000000..d860308 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/LzmaCheck.cs @@ -0,0 +1,34 @@ +namespace Packaging.Targets.IO +{ + /// + /// Type of the integrity check (Check ID) + /// + /// + /// The.xz format supports multiple types of checks that are calculated + /// from the uncompressed data. They vary in both speed and ability to + /// detect errors. + /// + /// + internal enum LzmaCheck + { + /// + /// No Check is calculated. + /// + None = 0, + + /// + /// CRC32 using the polynomial from the IEEE 802.3 standard + /// + Crc32 = 1, + + /// + /// CRC64 using the polynomial from the ECMA-182 standard + /// + Crc64 = 4, + + /// + /// SHA-256 + /// + Sha256 = 10 + } +} diff --git a/DebUOS/Packaging.Targets/IO/LzmaDecodeFlags.cs b/DebUOS/Packaging.Targets/IO/LzmaDecodeFlags.cs new file mode 100644 index 0000000..418de7f --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/LzmaDecodeFlags.cs @@ -0,0 +1,47 @@ +namespace Packaging.Targets.IO +{ + /// + /// Flags that control the decoding behavior of liblzma. + /// + internal enum LzmaDecodeFlags : uint + { + /// + /// This flag makes return + /// if the input stream + /// being decoded has no integrity check. + /// + TellNoCheck = 0x01, + + /// + /// This flag makes return + /// if the input + /// stream has an integrity check, but the type of the integrity check is not + /// supported by this liblzma version or build. Such files can still be + /// decoded, but the integrity check cannot be verified. + /// + TellUnsupportedCheck = 0x02, + + /// + /// This flag makes return + /// as soon as the type + /// of the integrity check is known. + /// + TellAnyCheck = 0x04, + + /// + /// + /// This flag enables decoding of concatenated files with file formats that + /// allow concatenating compressed files as is. From the formats currently + /// supported by liblzma, only the .xz format allows concatenated files. + /// Concatenated files are not allowed with the legacy .lzma format. + /// + /// + /// This flag also affects the usage of the `action' argument for + /// When is used, won't return + /// unless is used as `action'. Thus, the application has to set + /// in the same way as it does when encoding. + /// + /// + Concatenated = 0x08 + } +} diff --git a/DebUOS/Packaging.Targets/IO/LzmaMT.cs b/DebUOS/Packaging.Targets/IO/LzmaMT.cs new file mode 100644 index 0000000..38b55e1 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/LzmaMT.cs @@ -0,0 +1,154 @@ +using System; + +namespace Packaging.Targets.IO +{ +#pragma warning disable SA1307 // Accessible fields must begin with upper-case letter +#pragma warning disable SA1310 // Field names must not contain underscore +#pragma warning disable CS0169 // The field '' is never used +#pragma warning disable IDE0051 // Remove unused private members + /// + /// Multithreading options + /// + internal struct LzmaMT + { + /// + /// Set this to zero if no flags are wanted. + /// + /// + /// No flags are currently supported. + /// + public uint flags; + + /// + /// Number of worker threads to use + /// + public uint threads; + + /// + /// Maximum uncompressed size of a Block + /// + /// + /// + /// The encoder will start a new .xz Block every block_size bytes. + /// Using LZMA_FULL_FLUSH or LZMA_FULL_BARRIER with lzma_code() + /// the caller may tell liblzma to start a new Block earlier. + /// + /// + /// With LZMA2, a recommended block size is 2-4 times the LZMA2 + /// dictionary size. With very small dictionaries, it is recommended + /// to use at least 1 MiB block size for good compression ratio, even + /// if this is more than four times the dictionary size. Note that + /// these are only recommendations for typical use cases; feel free + /// to use other values. Just keep in mind that using a block size + /// less than the LZMA2 dictionary size is waste of RAM. + /// + /// + /// Set this to 0 to let liblzma choose the block size depending + /// on the compression options. For LZMA2 it will be 3*dict_size + /// or 1 MiB, whichever is more. + /// + /// + /// For each thread, about 3 * block_size bytes of memory will be + /// allocated. This may change in later liblzma versions. If so, + /// the memory usage will probably be reduced, not increased. + /// + /// + public ulong block_size; + + /// + /// Timeout to allow lzma_code() to return early + /// + /// + /// + /// Multithreading can make liblzma to consume input and produce + /// output in a very bursty way: it may first read a lot of input + /// to fill internal buffers, then no input or output occurs for + /// a while. + /// + /// + /// In single-threaded mode, lzma_code() won't return until it has + /// either consumed all the input or filled the output buffer. If + /// this is done in multithreaded mode, it may cause a call + /// lzma_code() to take even tens of seconds, which isn't acceptable + /// in all applications. + /// + /// + /// To avoid very long blocking times in lzma_code(), a timeout + /// (in milliseconds) may be set here. If lzma_code() would block + /// longer than this number of milliseconds, it will return with + /// LZMA_OK. Reasonable values are 100 ms or more. The xz command + /// line tool uses 300 ms. + /// + /// + /// If long blocking times are fine for you, set timeout to a special + /// value of 0, which will disable the timeout mechanism and will make + /// lzma_code() block until all the input is consumed or the output + /// buffer has been filled. + /// + /// + /// Even with a timeout, lzma_code() might sometimes take + /// somewhat long time to return. No timing guarantees + /// are made. + /// + /// + public uint timeout; + + /// + /// Compression preset (level and possible flags) + /// + /// + /// + /// The preset is set just like with lzma_easy_encoder(). + /// + /// + /// The preset is ignored if filters below is non-NULL. + /// + /// + public uint preset; + + /// + /// Filter chain (alternative to a preset). + /// + /// + /// If this is NULL, the preset above is used. Otherwise the preset + /// is ignored and the filter chain specified here is used. + /// + public IntPtr filters; + + /// + /// Integrity check type. + /// + /// + /// The xz command line tool defaults to LZMA_CHECK_CRC64, which is + /// a good choice if you are unsure. + /// + public LzmaCheck check; + + /// + /// Reserved space to allow possible future extensions without + /// breaking the ABI. You should not touch these, because the names + /// of these variables may change. These are and will never be used + /// with the currently supported options, so it is safe to leave these + /// uninitialized. + /// + private readonly int reserved_enum1; + private readonly int reserved_enum2; + private readonly int reserved_enum3; + private readonly int reserved_int1; + private readonly int reserved_int2; + private readonly int reserved_int3; + private readonly int reserved_int4; + private readonly ulong reserved_int5; + private readonly ulong reserved_int6; + private readonly ulong reserved_int7; + private readonly ulong reserved_int8; + private readonly IntPtr reserved_ptr1; + private readonly IntPtr reserved_ptr2; + private readonly IntPtr reserved_ptr3; + private readonly IntPtr reserved_ptr4; + } +#pragma warning restore SA1307 // Accessible fields must begin with upper-case letter +#pragma warning restore SA1310 // Field names must not contain underscore +#pragma warning restore CS0169 // The field '' is never used +#pragma warning restore IDE0051 // Remove unused private members +} diff --git a/DebUOS/Packaging.Targets/IO/LzmaResult.cs b/DebUOS/Packaging.Targets/IO/LzmaResult.cs new file mode 100644 index 0000000..03f4cc0 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/LzmaResult.cs @@ -0,0 +1,230 @@ +using System; + +namespace Packaging.Targets.IO +{ + /// + /// Return values used by several functions in liblzma. + /// + internal enum LzmaResult : uint + { + /// + /// The operation completed successfully. + /// + OK = 0, + + /// + /// End of stream was reached + /// + /// + /// + /// In encoder, , , or + /// was finished. In decoder, this indicates + /// that all the data was successfully decoded. + /// + /// + /// In all cases, when is returned, the last + /// output bytes should be picked from . + /// + /// + StreamEnd = 1, + + /// + /// Input stream has no integrity check + /// + /// + /// + /// This return value can be returned only if the + /// flag was used when initializing + /// the decoder. is just a warning, and + /// the decoding can be continued normally. + /// + /// + /// It is possible to call lzma_get_check() immediately after + /// lzma_code has returned . The result will + /// naturally be , but the possibility to call + /// lzma_get_check() may be convenient in some applications. + /// + /// + NoCheck = 2, + + /// + /// Cannot calculate the integrity check + /// + /// + /// + /// The usage of this return value is different in encoders + /// and decoders. + /// + /// + /// Encoders can return this value only from the initialization + /// function. If initialization fails with this value, the + /// encoding cannot be done, because there's no way to produce + /// output with the correct integrity check. + /// + /// + /// Decoders can return this value only from lzma_code() and + /// only if the flag was used when + /// initializing the decoder. The decoding can still be + /// continued normally even if the check type is unsupported, + /// but naturally the check will not be validated, and possible + /// errors may go undetected. + /// + /// + /// With decoder, it is possible to call lzma_get_check() + /// immediately after lzma_code() has returned + /// . This way it is possible to find + /// out what the unsupported Check ID was. + /// + /// + UnsupportedCheck = 3, + + /// + /// Integrity check type is now available + /// + /// + /// This value can be returned only by the lzma_code() function + /// and only if the decoder was initialized with the + /// flag. tells the + /// application that it may now call lzma_get_check() to find + /// out the Check ID. This can be used, for example, to + /// implement a decoder that accepts only files that have + /// strong enough integrity check. + /// + GetCheck = 4, + + /// + /// Cannot allocate memory + /// + /// + /// + /// Memory allocation failed, or the size of the allocation + /// would be greater than SIZE_MAX. + /// + /// + /// Due to internal implementation reasons, the coding cannot + /// be continued even if more memory were made available after + /// . + /// + /// + MemError = 5, + + /// + /// Memory usage limit was reached + /// + /// + /// Decoder would need more memory than allowed by the + /// specified memory usage limit. To continue decoding, + /// the memory usage limit has to be increased with + /// lzma_memlimit_set(). + /// + MemlimitError = 6, + + /// + /// File format not recognized + /// + /// + /// The decoder did not recognize the input as supported file + /// format. This error can occur, for example, when trying to + /// decode .lzma format file with , + /// because accepts only the .xz format. + /// + FormatError = 7, + + /// + /// Invalid or unsupported options + /// + /// + /// + /// Invalid or unsupported options, for example + /// - unsupported filter(s) or filter options; or + /// - reserved bits set in headers (decoder only). + /// + /// + /// Rebuilding liblzma with more features enabled, or + /// upgrading to a newer version of liblzma may help. + /// + /// + OptionsError = 8, + + /// + /// Data is corrupt + /// + /// + /// + /// The usage of this return value is different in encoders + /// and decoders. In both encoder and decoder, the coding + /// cannot continue after this error. + /// + /// + /// Encoders return this if size limits of the target file + /// format would be exceeded. These limits are huge, thus + /// getting this error from an encoder is mostly theoretical. + /// For example, the maximum compressed and uncompressed + /// size of a .xz Stream is roughly 8 EiB (2^63 bytes). + /// + /// + /// Decoders return this error if the input data is corrupt. + /// This can mean, for example, invalid CRC32 in headers + /// or invalid check of uncompressed data. + /// + /// + DataError = 9, + + /// + /// No progress is possible + /// + /// + /// + /// This error code is returned when the coder cannot consume + /// any new input and produce any new output.The most common + /// reason for this error is that the input stream being + /// decoded is truncated or corrupt. + /// + /// + /// This error is not fatal. Coding can be continued normally + /// by providing more input and/or more output space, if + /// possible. + /// + /// + /// Typically the first call to that can do no + /// progress returns instead of . Only + /// the second consecutive call doing no progress will return + /// . This is intentional. + /// + /// + /// With zlib, Z_BUF_ERROR may be returned even if the + /// application is doing nothing wrong, so apps will need + /// to handle Z_BUF_ERROR specially. The above hack + /// guarantees that liblzma never returns + /// to properly written applications unless the input file + /// is truncated or corrupt. This should simplify the + /// applications a little. + /// + /// + BufferError = 10, + + /// + /// Programming error + /// + /// + /// + /// This indicates that the arguments given to the function are + /// invalid or the internal state of the decoder is corrupt. + /// - Function arguments are invalid or the structures + /// pointed by the argument pointers are invalid + /// e.g. if has been set to and + /// > 0 when calling . + /// - lzma_/// functions have been called in wrong order + /// e.g. was called right after . + /// - If errors occur randomly, the reason might be flaky + /// hardware. + /// + /// + /// If you think that your code is correct, this error code + /// can be a sign of a bug in liblzma. See the documentation + /// how to report bugs. + /// + /// + ProgError = 11 + } +} diff --git a/DebUOS/Packaging.Targets/IO/LzmaStream.cs b/DebUOS/Packaging.Targets/IO/LzmaStream.cs new file mode 100644 index 0000000..6ed09a7 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/LzmaStream.cs @@ -0,0 +1,121 @@ +using System; +using System.Runtime.InteropServices; + +namespace Packaging.Targets.IO +{ + /// + /// Passing data to and from liblzma + /// + /// + /// + /// The lzma_stream structure is used for + /// - passing pointers to input and output buffers to liblzma; + /// - defining custom memory hander functions; and + /// - holding a pointer to coder-specific internal data structures. + /// + /// + /// Typical usage: + /// + /// + /// - After allocating (on stack or with malloc()), it must be + /// initialized to LZMA_STREAM_INIT (see LZMA_STREAM_INIT for details). + /// + /// + /// - Initialize a coder to the lzma_stream, for example by using + /// lzma_easy_encoder() or lzma_auto_decoder(). Some notes: + /// - In contrast to zlib, and are + /// ignored by all initialization functions, thus it is safe + /// to not initialize them yet. + /// - The initialization functions always set strm->total_in and + /// strm->total_out to zero. + /// - If the initialization function fails, no memory is left allocated + /// that would require freeing with even if some memory was + /// associated with the structure when the initialization + /// function was called. + /// + /// + /// - Use to do the actual work. + /// + /// + /// - Once the coding has been finished, the existing lzma_stream can be + /// reused. It is OK to reuse with different initialization + /// function without calling first. Old allocations are + /// automatically freed. + /// + /// + /// - Finally, use to free the allocated memory. never + /// frees the structure itself. + /// + /// + /// Application may modify the values of and as it wants. + /// They are updated by liblzma to match the amount of data read and + /// written, but aren't used for anything else. + /// + /// + [StructLayout(LayoutKind.Sequential)] + internal struct LzmaStream + { + /// + /// Pointer to the next input byte. + /// + public IntPtr NextIn; + + /// + /// Number of available input bytes in next_in. + /// + public uint AvailIn; + + /// + /// Total number of bytes read by liblzma. + /// + public ulong TotalIn; + + /// + /// Pointer to the next output position. + /// + public IntPtr NextOut; + + /// + /// Amount of free space in next_out. + /// + public uint AvailOut; + + /// + /// Total number of bytes written by liblzma. + /// + public ulong TotalOut; + + /// + /// Custom memory allocation functions + /// + /// + /// In most cases this is NULL which makes liblzma use + /// the standard malloc() and free(). + /// + public IntPtr Allocator; + + /// + /// Internal state is not visible to applications. + /// +#pragma warning disable SA1214 // Readonly fields must appear before non-readonly fields + public readonly IntPtr InternalState; +#pragma warning restore SA1214 // Readonly fields must appear before non-readonly fields + + /// + /// Reserved space to allow possible future extensions without + /// breaking the ABI. Excluding the initialization of this structure, + /// you should not touch these, because the names of these variables + /// may change. + /// + private readonly IntPtr reservedPtr1; + private readonly IntPtr reservedPtr2; + private readonly IntPtr reservedPtr3; + private readonly IntPtr reservedPtr4; + private readonly ulong reservedInt1; + private readonly ulong reservedInt2; + private readonly uint reservedInt3; + private readonly uint reservedInt4; + private readonly uint reservedEnum1; + private readonly uint reservedEnum2; + } +} \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/IO/LzmaStreamFlags.cs b/DebUOS/Packaging.Targets/IO/LzmaStreamFlags.cs new file mode 100644 index 0000000..d235a3f --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/LzmaStreamFlags.cs @@ -0,0 +1,98 @@ +using System.Runtime.InteropServices; + +namespace Packaging.Targets.IO +{ + /// + /// Options for encoding/decoding Stream Header and Stream Footer + /// + /// + [StructLayout(LayoutKind.Sequential)] + internal struct LzmaStreamFlags + { + /// + /// Stream Flags format version + /// + /// + /// + /// To prevent API and ABI breakages if new features are needed in + /// Stream Header or Stream Footer, a version number is used to + /// indicate which fields in this structure are in use.For now, + /// version must always be zero.With non-zero version, the + /// lzma_stream_header_encode() and lzma_stream_footer_encode() + /// will return . + /// + /// + /// + /// lzma_stream_header_decode() and + /// will always set this to the lowest value that supports all the + /// features indicated by the Stream Flags field.The application + /// must check that the version number set by the decoding functions + /// is supported by the application. Otherwise it is possible that + /// the application will decode the Stream incorrectly. + /// + /// + public readonly uint Version; + + /// + /// Backward Size + /// + /// + /// + /// Backward Size must be a multiple of four bytes.In this Stream + /// format version, Backward Size is the size of the Index field. + /// + /// + /// + /// Backward Size isn't actually part of the Stream Flags field, but + /// it is convenient to include in this structure anyway. Backward + /// Size is present only in the Stream Footer.There is no need to + /// initialize backward_size when encoding Stream Header. + /// + /// + /// + /// lzma_stream_header_decode() always sets backward_size to + /// LZMA_VLI_UNKNOWN so that it is convenient to use + /// lzma_stream_flags_compare() when both Stream Header and Stream + /// Footer have been decoded. + /// + /// + public ulong BackwardSize; + + /// + /// Check ID + /// + /// + /// This indicates the type of the integrity check calculated from + /// uncompressed data. + /// + public LzmaCheck Check; + + /// + /// + /// Reserved space to allow possible future extensions without + /// breaking the ABI.You should not touch these, because the + /// names of these variables may change. + /// + /// + /// + /// (We will never be able to use all of these since Stream Flags + /// is just two bytes plus Backward Size of four bytes.But it's + /// nice to have the proper types when they are needed.) + /// + /// + private readonly int reservedEnum1; + private readonly int reservedEnum2; + private readonly int reservedEnum3; + private readonly int reservedEnum4; + private readonly char reservedBool1; + private readonly char reservedBool2; + private readonly char reservedBool3; + private readonly char reservedBool4; + private readonly char reservedBool5; + private readonly char reservedBool6; + private readonly char reservedBool7; + private readonly char reservedBool8; + private readonly uint reservedInt1; + private readonly uint reservedInt2; + } +} diff --git a/DebUOS/Packaging.Targets/IO/NativeMethods.cs b/DebUOS/Packaging.Targets/IO/NativeMethods.cs new file mode 100644 index 0000000..9b73f34 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/NativeMethods.cs @@ -0,0 +1,407 @@ +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using FunctionLoader = Packaging.Targets.Native.FunctionLoader; + +namespace Packaging.Targets.IO +{ + /// + /// Provides access to the liblzma API. liblzma is part of the xz suite. + /// + /// + /// You can download pre-built binaries from Windows from https://tukaani.org/xz/. + /// + /// + /// + internal static class NativeMethods + { + /// + /// The name of the lzma library. + /// + /// + /// You can fetch liblzma from https://github.com/RomanBelkov/XZ.NET/blob/master/XZ.NET/liblzma.dll + /// + private const string LibraryName = @"lzma"; + + private static lzma_stream_decoder_delegate lzma_stream_decoder_ptr; + private static lzma_code_delegate lzma_code_ptr; + private static lzma_stream_footer_decode_delegate lzma_stream_footer_decode_ptr; + private static lzma_index_uncompressed_size_delegate lzma_index_uncompressed_size_ptr; + private static lzma_index_buffer_decode_delegate lzma_index_buffer_decode_ptr; + private static lzma_index_end_delegate lzma_index_end_ptr; + private static lzma_end_delegate lzma_end_ptr; + private static lzma_easy_encoder_delegate lzma_easy_encoder_ptr; + private static lzma_stream_encoder_mt_delegate lzma_stream_encoder_mt_ptr; + private static lzma_stream_buffer_bound_delegate lzma_stream_buffer_bound_ptr; + private static lzma_easy_buffer_encode_delegate lzma_easy_buffer_encode_ptr; + + static NativeMethods() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + if (RuntimeInformation.OSArchitecture != Architecture.X64) + { + throw new InvalidOperationException(".NET packaging only supports 64-bit Windows processes"); + } + } + + var libraryPath = Path.GetDirectoryName(typeof(NativeMethods).GetTypeInfo().Assembly.Location); + var lzmaWindowsPath = Path.GetFullPath(Path.Combine(libraryPath, "../../runtimes/win7-x64/native/lzma.dll")); + + IntPtr library = FunctionLoader.LoadNativeLibrary( + new string[] { lzmaWindowsPath, "lzma.dll" }, // lzma.dll is used when running unit tests. + new string[] { "liblzma.so.5", "liblzma.so" }, + new string[] { "liblzma.dylib" }); + + if (library == IntPtr.Zero) + { + throw new FileLoadException("Could not load liblzma. On Linux, make sure you've installed liblzma-dev or an equivalent package."); + } + + lzma_stream_decoder_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_stream_decoder)); + lzma_code_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_code)); + lzma_stream_footer_decode_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_stream_footer_decode)); + lzma_index_uncompressed_size_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_index_uncompressed_size)); + lzma_index_buffer_decode_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_index_buffer_decode)); + lzma_index_end_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_index_end)); + lzma_end_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_end)); + lzma_easy_encoder_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_easy_encoder)); + lzma_stream_encoder_mt_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_stream_encoder_mt), throwOnError: false); + lzma_stream_buffer_bound_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_stream_buffer_bound)); + lzma_easy_buffer_encode_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_easy_buffer_encode)); + } + + private delegate LzmaResult lzma_stream_decoder_delegate(ref LzmaStream stream, ulong memLimit, LzmaDecodeFlags flags); + + private unsafe delegate LzmaResult lzma_easy_buffer_encode_delegate(uint preset, LzmaCheck check, void* allocator, byte[] @in, UIntPtr in_size, byte[] @out, UIntPtr* out_pos, UIntPtr out_size); + + private delegate UIntPtr lzma_stream_buffer_bound_delegate(UIntPtr uncompressed_size); + + private delegate LzmaResult lzma_stream_encoder_mt_delegate(ref LzmaStream stream, ref LzmaMT mt); + + private delegate LzmaResult lzma_easy_encoder_delegate(ref LzmaStream stream, uint preset, LzmaCheck check); + + private delegate void lzma_end_delegate(ref LzmaStream stream); + + private delegate void lzma_index_end_delegate(IntPtr i, IntPtr allocator); + + private delegate uint lzma_index_buffer_decode_delegate(ref IntPtr i, ref ulong memLimit, IntPtr allocator, byte[] indexBuffer, ref uint inPosition, ulong inSize); + + private delegate ulong lzma_index_uncompressed_size_delegate(IntPtr i); + + private delegate LzmaResult lzma_stream_footer_decode_delegate(ref LzmaStreamFlags options, byte[] inp); + + private delegate LzmaResult lzma_code_delegate(ref LzmaStream stream, LzmaAction action); + + /// + /// Gets a value indicating whether the underlying native library supports multithreading. + /// + public static bool SupportsMultiThreading + { + get { return lzma_stream_encoder_mt_ptr != null; } + } + + /// + /// Initialize .xz Stream decoder + /// + /// + /// Pointer to properly prepared + /// + /// + /// Memory usage limit as bytes. Use UINT64_MAX + /// to effectively disable the limiter. + /// + /// + /// Bitwise-or of zero or more of the decoder flags: + /// , , + /// , . + /// + /// + /// : Initialization was successful, + /// : Cannot allocate memory, + /// : Unsupported flags, + /// . + /// + /// + public static LzmaResult lzma_stream_decoder(ref LzmaStream stream, ulong memLimit, LzmaDecodeFlags flags) => lzma_stream_decoder_ptr(ref stream, memLimit, flags); + + /// + /// Encode or decode data + /// + /// + /// The for which to read the data. + /// + /// + /// The action to perform. + /// + /// + /// A value. + /// + /// + /// + /// Once the has been successfully initialized (e.g. with + /// lzma_stream_encoder()), the actual encoding or decoding is done + /// using this function.The application has to update , + /// , , and to pass input + /// to and get output from liblzma. + /// + /// + /// See the description of the coder-specific initialization function to find + /// out what values are supported by the coder. + /// + /// + public static LzmaResult lzma_code(ref LzmaStream stream, LzmaAction action) => lzma_code_ptr(ref stream, action); + + /// + /// Decode Stream Footer + /// + /// + /// Target for the decoded Stream Header options. + /// + /// + /// Beginning of the input buffer of + /// LZMA_STREAM_HEADER_SIZE bytes. + /// + /// + /// : Decoding was successful. + /// : Magic bytes don't match, thus the given buffer cannot be Stream Footer. + /// : CRC32 doesn't match, thus the Stream Footer is corrupt. + /// : Unsupported options are present in Stream Footer. + /// + /// + /// If Stream Header was already decoded successfully, but + /// decoding Stream Footer returns , the + /// application should probably report some other error message + /// than "file format not recognized", since the file more likely + /// is corrupt(possibly truncated). Stream decoder in liblzma + /// uses in this situation. + /// + /// + public static LzmaResult lzma_stream_footer_decode(ref LzmaStreamFlags options, byte[] inp) => lzma_stream_footer_decode_ptr(ref options, inp); + + /// + /// Get the uncompressed size of the file + /// + /// + public static ulong lzma_index_uncompressed_size(IntPtr i) => lzma_index_uncompressed_size_ptr(i); + + /// + /// Single-call .xz Index decoder + /// + /// If decoding succeeds, will point to a new lzma_index, which the application has to + /// later free with lzma_index_end(). If an error occurs, will be . The old value of + /// is always ignored and thus doesn't need to be initialized by the caller. + /// + /// + /// Pointer to how much memory the resulting lzma_index is allowed to require. The value + /// pointed by this pointer is modified if and only if is returned. + /// + /// + /// Pointer to lzma_allocator, or to use malloc() + /// + /// + /// Beginning of the input buffer + /// + /// + /// The next byte will be read from in[*in_pos]. *in_pos is updated only if decoding succeeds. + /// + /// + /// Size of the input buffer; the first byte that won't be read is in[in_size]. + /// + /// + /// : Decoding was successful. + /// , + /// : Memory usage limit was reached. The minimum required memlimit value was stored to* memlimit. + /// , + /// . + /// + /// + public static uint lzma_index_buffer_decode(ref IntPtr i, ref ulong memLimit, IntPtr allocator, byte[] indexBuffer, ref uint inPosition, ulong inSize) + => lzma_index_buffer_decode_ptr(ref i, ref memLimit, allocator, indexBuffer, ref inPosition, inSize); + + /// + /// Deallocate lzma_index + /// + /// + /// The index to deallocate + /// + /// + /// The allocated used to allocate the memory. + /// + /// + /// If is , this does nothing. + /// + /// + public static void lzma_index_end(IntPtr i, IntPtr allocator) => lzma_index_end_ptr(i, allocator); + + /// + /// Free memory allocated for the coder data structures + /// + /// + /// Pointer to lzma_stream that is at least initialized with LZMA_STREAM_INIT. + /// + /// + /// After , is guaranteed to be . + /// No other members of the structure are touched. + /// zlib indicates an error if application end()s unfinished stream structure. + /// liblzma doesn't do this, and assumes that + /// application knows what it is doing. + /// + /// + public static void lzma_end(ref LzmaStream stream) => lzma_end_ptr(ref stream); + + /// + /// + /// Initialize .xz Stream encoder using a preset number. + /// + /// + /// This function is intended for those who just want to use the basic features if liblzma(that is, most developers out there). + /// + /// + /// If initialization fails(return value is not LZMA_OK), all the memory allocated for *strm by liblzma is always freed.Thus, there is no need to call lzma_end() after failed initialization. + /// + /// If initialization succeeds, use lzma_code() to do the actual encoding.Valid values for `action' (the second argument of lzma_code()) are LZMA_RUN, LZMA_SYNC_FLUSH, LZMA_FULL_FLUSH, and LZMA_FINISH. In future, there may be compression levels or flags that don't support LZMA_SYNC_FLUSH. + /// + /// + /// + /// Pointer to lzma_stream that is at least initialized with LZMA_STREAM_INIT. + /// + /// + /// Compression preset to use. A preset consist of level number and zero or more flags. Usually flags aren't used, so preset is simply a number [0, 9] which match the options -0 ... -9 of the xz command line tool. Additional flags can be be set using bitwise-or with the preset level number, e.g. 6 | LZMA_PRESET_EXTREME. + /// + /// + /// Integrity check type to use. See check.h for available checks. The xz command line tool defaults to LZMA_CHECK_CRC64, which is a good choice if you are unsure. LZMA_CHECK_CRC32 is good too as long as the uncompressed file is not many gigabytes. + /// + /// + /// + /// - LZMA_OK: Initialization succeeded. Use lzma_code() to encode your data. + /// + /// - LZMA_MEM_ERROR: Memory allocation failed. + /// + /// + /// - LZMA_OPTIONS_ERROR: The given compression preset is not supported by this build of liblzma. + /// + /// + /// - LZMA_UNSUPPORTED_CHECK: The given check type is not supported by this liblzma build. + /// + /// + /// - LZMA_PROG_ERROR: One or more of the parameters have values that will never be valid. For example, strm == NULL. + /// + /// + public static LzmaResult lzma_easy_encoder(ref LzmaStream stream, uint preset, LzmaCheck check) => lzma_easy_encoder_ptr(ref stream, preset, check); + + /// + /// + /// Initialize multithreaded .xz Stream encoder + /// + /// + /// This provides the functionality of lzma_easy_encoder() and + /// lzma_stream_encoder() as a single function for multithreaded use. + /// + /// + /// The supported actions for lzma_code() are LZMA_RUN, LZMA_FULL_FLUSH, + /// LZMA_FULL_BARRIER, and LZMA_FINISH. Support for LZMA_SYNC_FLUSH might be + /// added in the future. + /// + /// + /// + /// Pointer to properly prepared lzma_stream + /// + /// + /// Pointer to multithreaded compression options + /// + /// + /// A value which indicates success or failure. + /// + public static LzmaResult lzma_stream_encoder_mt(ref LzmaStream stream, ref LzmaMT mt) + { + if (SupportsMultiThreading) + { + return lzma_stream_encoder_mt_ptr(ref stream, ref mt); + } + else + { + throw new PlatformNotSupportedException("lzma_stream_encoder_mt is not supported on this platform. Check SupportsMultiThreading to see whether you can use this functionality."); + } + } + + /// + /// + /// Calculate output buffer size for single-call Stream encoder + /// + /// + /// When trying to compress uncompressible data, the encoded size will be slightly bigger than the input data.This function calculates how much output buffer space is required to be sure that lzma_stream_buffer_encode() doesn't return LZMA_BUF_ERROR. + /// + /// + /// The calculated value is not exact, but it is guaranteed to be big enough.The actual maximum output space required may be slightly smaller (up to about 100 bytes). This should not be a problem in practice. + /// + /// + /// If the calculated maximum size doesn't fit into size_t or would make the Stream grow past LZMA_VLI_MAX (which should never happen in practice), zero is returned to indicate the error. + /// + /// + /// + /// The uncompressed size. + /// + /// + /// The buffer output size. + /// + /// + /// The limit calculated by this function applies only to single-call encoding. Multi-call encoding may (and probably will) have larger maximum expansion when encoding uncompressible data. Currently there is no function to calculate the maximum expansion of multi-call encoding. + /// + public static UIntPtr lzma_stream_buffer_bound(UIntPtr uncompressed_size) => lzma_stream_buffer_bound_ptr(uncompressed_size); + + /// + /// Single-call .xz Stream encoding using a preset number. + /// + /// + /// Compression preset to use. See the description in lzma_easy_encoder(). + /// + /// + /// Type of the integrity check to calculate from uncompressed data. + /// + /// + /// lzma_allocator for custom allocator functions. Set to NULL to use malloc() and free(). + /// + /// + /// Beginning of the input buffer + /// + /// + /// Size of the input buffer + /// + /// + /// Beginning of the output buffer + /// + /// + /// The next byte will be written to out[*out_pos]. *out_pos is updated only if encoding succeeds. + /// + /// + /// Size of the out buffer; the first byte into which no data is written to is out[out_size]. + /// + /// + /// + /// - LZMA_OK: Encoding was successful. + /// + /// + /// - LZMA_BUF_ERROR: Not enough output buffer space. + /// + /// + /// - LZMA_OPTIONS_ERROR + /// + /// + /// - LZMA_MEM_ERROR + /// + /// + /// - LZMA_DATA_ERROR + /// + /// + /// - LZMA_PROG_ERROR + /// + /// + /// + /// The maximum required output buffer size can be calculated with lzma_stream_buffer_bound() + /// + public static unsafe LzmaResult lzma_easy_buffer_encode(uint preset, LzmaCheck check, void* allocator, byte[] @in, UIntPtr in_size, byte[] @out, UIntPtr* out_pos, UIntPtr out_size) + => lzma_easy_buffer_encode_ptr(preset, check, allocator, @in, in_size, @out, out_pos, out_size); + } +} diff --git a/DebUOS/Packaging.Targets/IO/SubStream.cs b/DebUOS/Packaging.Targets/IO/SubStream.cs new file mode 100644 index 0000000..edaf944 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/SubStream.cs @@ -0,0 +1,380 @@ +using System; +using System.IO; + +namespace Packaging.Targets.IO +{ + /// + /// Represents parts of a , from a start byte offset for a given length. + /// + public class SubStream : Stream + { + /// + /// The parent stream of this stream. + /// + private Stream stream; + + /// + /// The offset, that is, the location at which the starts. + /// + private long subStreamOffset; + + /// + /// The length of the . + /// + private long subStreamLength; + + /// + /// A value indicating whether the parent stream should be closed when this + /// is closed, or not. + /// + private bool leaveParentOpen; + + /// + /// A value indicating whether this should only support read-only operations. + /// + private bool readOnly; + + /// + /// The current position of the . This allows us keep the + /// consistent accross multiple calls, even if the position of changes (e.g. because + /// another operates on it). + /// + private long position; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The parent stream of this stream. + /// + /// + /// The offset at which the stream starts. + /// + /// + /// The length of the . + /// + /// + /// A value indicating whether the parent stream should be closed when this + /// is closed, or not. + /// + /// + /// A value indicating whether the be opened in read-only mode or not. + /// + public SubStream(Stream stream, long offset, long length, bool leaveParentOpen = false, bool readOnly = false) + { + this.stream = stream; + this.subStreamOffset = offset; + this.subStreamLength = length; + this.leaveParentOpen = leaveParentOpen; + this.readOnly = readOnly; + this.position = 0; + + if (this.stream.CanSeek) + { + this.Seek(0, SeekOrigin.Begin); + } + } + + /// + /// Gets a value indicating whether the current stream supports reading + /// + public override bool CanRead + { + get + { + return this.stream.CanRead; + } + } + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + public override bool CanSeek + { + get + { + return this.stream.CanSeek; + } + } + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + public override bool CanWrite + { + get + { + return !this.readOnly && this.stream.CanWrite; + } + } + + /// + /// Gets the length in bytes of the stream. + /// + public override long Length + { + get + { + return this.subStreamLength; + } + } + + /// + /// Gets or sets the position within the current . + /// + public override long Position + { + get + { + return this.position; + } + + set + { + lock (this.stream) + { + this.stream.Position = value + this.Offset; + this.position = value; + } + } + } + + /// + /// Gets the parent stream of this . + /// + internal Stream Stream + { + get + { + return this.stream; + } + } + + /// + /// Gets the offset at which the starts. + /// + internal long Offset + { + get + { + return this.subStreamOffset; + } + } + + /// + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// + public override void Flush() + { + lock (this.stream) + { + this.stream.Flush(); + } + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset + /// and (offset + count - 1) replaced by the bytes read from the current source. + /// + /// + /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. + /// + /// + /// The maximum number of bytes to be read from the current stream. + /// + /// + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are + /// not currently available, or zero (0) if the end of the stream has been reached. + /// + public override int Read(byte[] buffer, int offset, int count) + { + lock (this.stream) + { + this.EnsurePosition(); + + // Make sure we don't pass the size of the substream + long bytesRemaining = this.Length - this.Position; + long bytesToRead = Math.Min(count, bytesRemaining); + + if (bytesToRead < 0) + { + bytesToRead = 0; + } + + var read = this.stream.Read(buffer, offset, (int)bytesToRead); + this.position += read; + return read; + } + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position + /// within this stream by the number of bytes written. + /// + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + /// + /// + /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. + /// + /// + /// The number of bytes to be written to the current stream. + /// + public override void Write(byte[] buffer, int offset, int count) + { + if (this.readOnly) + { + throw new NotSupportedException(); + } + + lock (this.stream) + { + this.EnsurePosition(); + + if (this.Position + offset + count > this.Length || this.Position < 0) + { + throw new InvalidOperationException("This write operation would exceed the current length of the substream."); + } + + this.stream.Write(buffer, offset, count); + this.position += count; + } + } + + /// + /// Writes a byte to the current position in the stream and advances the position within the stream by one byte. + /// + /// + /// The byte to write to the stream. + /// + public override void WriteByte(byte value) + { + if (this.readOnly) + { + throw new NotSupportedException(); + } + + lock (this.stream) + { + this.EnsurePosition(); + + if (this.Position > this.Length || this.Position < 0) + { + throw new InvalidOperationException("This write operation would exceed the current length of the substream."); + } + + this.stream.WriteByte(value); + this.position++; + } + } + + /// + /// Sets the position within the current stream. + /// + /// + /// A byte offset relative to the origin parameter. + /// + /// + /// A value of type SeekOrigin indicating the reference point used to obtain the new position. + /// + /// + /// The new position within the current stream. + /// + public override long Seek(long offset, SeekOrigin origin) + { + lock (this.stream) + { + switch (origin) + { + case SeekOrigin.Begin: + offset += this.subStreamOffset; + break; + + case SeekOrigin.End: + long enddelta = this.subStreamOffset + this.subStreamLength - this.stream.Length; + offset += enddelta; + break; + + case SeekOrigin.Current: + // Nothing to do, because we'll pass SeekOrigin.Current to the + // parent stream. + break; + } + + // If we're doing an absolute seek, we don't care about the position, + // but if the seek is relative, make sure we start from the correct position + if (origin == SeekOrigin.Current) + { + this.EnsurePosition(); + } + + var parentPosition = this.stream.Seek(offset, origin); + this.position = parentPosition - this.Offset; + return this.position; + } + } + + /// + /// Sets the length of the current stream. + /// + /// + /// The desired length of the current stream in bytes. + /// + public override void SetLength(long value) + { + if (this.readOnly) + { + throw new NotSupportedException(); + } + + this.subStreamLength = value; + } + + /// + /// Updates the size of this relative to its parent stream. + /// + /// + /// The new offset. + /// + /// + /// The new length. + /// + public void UpdateWindow(long offset, long length) + { + this.subStreamOffset = offset; + this.subStreamLength = length; + } + + /// + protected override void Dispose(bool disposing) + { + if (!this.leaveParentOpen) + { + this.stream.Dispose(); + } + + base.Dispose(disposing); + } + + /// + /// Makes sure the position of the parent stream is aligned with the position we have stored locally. + /// + /// + /// Take a scenario where you have two objects that navigate the same . + /// They can both seek independently, so the parent's position will change without the other + /// knowing about it. Calling corrects that, enabling scenarios where you can synchronously + /// do I/O on both streams: things like should start working. + /// This will, however, not work for multi-threaded access. + /// + private void EnsurePosition() + { + if (this.stream.Position != this.position + this.Offset) + { + this.stream.Position = this.position + this.Offset; + } + } + } +} diff --git a/DebUOS/Packaging.Targets/IO/TarFile.cs b/DebUOS/Packaging.Targets/IO/TarFile.cs new file mode 100644 index 0000000..48f708a --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/TarFile.cs @@ -0,0 +1,86 @@ +using System; +using System.IO; + +namespace Packaging.Targets.IO +{ + /// + /// Represents a Tar archive. + /// + public class TarFile : ArchiveFile + { + private TarHeader entryHeader; + + /// + /// Initializes a new instance of the class. + /// + /// + /// A which represents the tar file. + /// + /// + /// to leave the underlying open when this + /// is disposed of; otherwise, . + /// + public TarFile(Stream stream, bool leaveOpen) + : base(stream, leaveOpen) + { + } + + /// + /// Gets the link target of this file. + /// + public string LinkName + { + get; + private set; + } + + /// + public override bool Read() + { + this.Align(512); + this.EntryStream?.Dispose(); + this.EntryStream = null; + + this.entryHeader = this.Stream.ReadStruct(); + this.FileHeader = this.entryHeader; + this.FileName = this.entryHeader.FileName; + this.LinkName = this.entryHeader.LinkName; + + // There are two empty blocks at the end of the file. + if (string.IsNullOrEmpty(this.entryHeader.Magic)) + { + return false; + } + + if (this.entryHeader.Magic != "ustar") + { + throw new InvalidDataException("The magic for the file entry is invalid"); + } + + this.Align(512); + + // TODO: Validate Checksum + this.EntryStream = new SubStream(this.Stream, this.Stream.Position, this.entryHeader.FileSize, leaveParentOpen: true); + + if (this.entryHeader.TypeFlag == TarTypeFlag.LongName) + { + var longFileName = this.ReadAsUtf8String(); + var read = this.Read(); + this.FileName = longFileName; + + return read; + } + else if (this.entryHeader.TypeFlag == TarTypeFlag.LongLink) + { + var longLinkName = this.ReadAsUtf8String(); + var read = this.Read(); + this.LinkName = longLinkName; + return read; + } + else + { + return true; + } + } + } +} diff --git a/DebUOS/Packaging.Targets/IO/TarFileCreator.cs b/DebUOS/Packaging.Targets/IO/TarFileCreator.cs new file mode 100644 index 0000000..db2eebd --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/TarFileCreator.cs @@ -0,0 +1,176 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Packaging.Targets.IO +{ + internal static class TarFileCreator + { + /// + /// Generates a based on a list of + /// values. + /// + /// + /// The values based on which to generate the . + /// + /// + /// The which will hold the . + /// + public static void FromArchiveEntries(List archiveEntries, Stream targetStream) + { + using (TarFile cpioFile = new TarFile(targetStream, leaveOpen: true)) + { + foreach (var entry in archiveEntries) + { + WriteEntry(targetStream, entry); + } + + WriteTrailer(targetStream); + } + } + + public static void WriteTrailer(Stream stream) + { + // The stream should already be aligned; as it is aligned after every entry. + // As a safety measure, for streams which can report on .Position, align the + // stream again. + if (stream.CanSeek) + { + Align(stream); + } + + var trailer = new byte[1024]; + stream.Write(trailer, 0, trailer.Length); + } + + public static void WriteEntry(Stream stream, TarHeader header, Stream data) + { + header.Checksum = header.ComputeChecksum(); + int written = stream.WriteStruct(header); + Align(stream, written); + data.CopyTo(stream); + Align(stream, data.Length); + } + + public static void WriteEntry(Stream stream, ArchiveEntry entry, Stream data = null) + { + var targetPath = entry.TargetPath; + if (!targetPath.StartsWith(".")) + { + targetPath = "." + targetPath; + } + + // Handle long file names (> 99 characters). If this is the case, add a "././@LongLink" pseudo-entry + // which contains the full name. + if (targetPath.Length > 99) + { + // Must include a trailing \0 + var nameLength = Encoding.UTF8.GetByteCount(targetPath); + byte[] entryName = new byte[nameLength + 1]; + + Encoding.UTF8.GetBytes(targetPath, 0, targetPath.Length, entryName, 0); + + ArchiveEntry nameEntry = new ArchiveEntry() + { + Mode = entry.Mode, + Modified = entry.Modified, + TargetPath = "././@LongLink", + Owner = entry.Owner, + Group = entry.Group + }; + + using (MemoryStream nameStream = new MemoryStream(entryName)) + { + WriteEntry(stream, nameEntry, nameStream); + } + + targetPath = targetPath.Substring(0, 99); + } + + var isDir = entry.Mode.HasFlag(LinuxFileMode.S_IFDIR); + var isLink = !isDir && !string.IsNullOrWhiteSpace(entry.LinkTo); + var isFile = !isDir && !isLink; + + TarTypeFlag type; + + if (entry.TargetPath == "././@LongLink") + { + type = TarTypeFlag.LongName; + } + else if (isFile) + { + type = TarTypeFlag.RegType; + } + else if (isDir) + { + type = TarTypeFlag.DirType; + } + else + { + type = TarTypeFlag.SymType; + } + + bool dispose = false; + if (data == null) + { + if (isFile) + { + dispose = true; + data = File.OpenRead(entry.SourceFilename); + } + else + { + data = new MemoryStream(); + } + } + + try + { + var hdr = new TarHeader() + { + // No need to set the file type, the tar header has a special field for that. + FileMode = entry.Mode & LinuxFileMode.PermissionsMask, + DevMajor = null, + DevMinor = null, + FileName = targetPath, + FileSize = (uint)data.Length, + GroupId = 0, + UserId = 0, + GroupName = entry.Group, + LinkName = isLink ? entry.LinkTo : string.Empty, + Prefix = string.Empty, + TypeFlag = type, + UserName = entry.Owner, + Version = null, + LastModified = entry.Modified, + Magic = "ustar" + }; + WriteEntry(stream, hdr, data); + } + finally + { + if (dispose) + { + data.Dispose(); + } + } + } + + private static void Align(Stream stream) + { + Align(stream, stream.Position); + } + + private static void Align(Stream stream, long position) + { + var spos = position % 512; + if (spos == 0) + { + return; + } + + var align = new byte[512 - spos]; + stream.Write(align, 0, align.Length); + } + } +} \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/IO/TarHeader.cs b/DebUOS/Packaging.Targets/IO/TarHeader.cs new file mode 100644 index 0000000..ca9bde8 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/TarHeader.cs @@ -0,0 +1,267 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +namespace Packaging.Targets.IO +{ + /// + /// Represents the header for an individual entry in a .tar archive. + /// + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct TarHeader : IArchiveHeader + { + private static readonly string Empty8 = new string('\0', 8); + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 100)] + private byte[] name; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] + private byte[] mode; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] + private byte[] uid; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] + private byte[] gid; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 12)] + private byte[] size; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 12)] + private byte[] mtime; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] + private byte[] chksum; + + private byte typeflag; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 100)] + private byte[] linkname; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 6)] + private byte[] magic; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 2)] + private byte[] version; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 32)] + private byte[] uname; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 32)] + private byte[] gname; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] + private byte[] devmajor; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 8)] + private byte[] devminor; + + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 155)] + private byte[] prefix; + + /// + /// Gets or sets the name of the current file. + /// + public string FileName + { + get => this.GetString(this.name, 100); + set => this.name = this.CreateString(value, 100); + } + + public LinuxFileMode FileMode + { + get => (LinuxFileMode)Convert.ToUInt32(this.GetString(this.mode, 8), 8); + set => this.mode = this.GetUIntTo8((uint)value); + } + + public uint UserId + { + get => Convert.ToUInt32(this.GetString(this.uid, 8), 8); + set => this.uid = this.GetUIntTo8(value); + } + + public uint GroupId + { + get => Convert.ToUInt32(this.GetString(this.gid, 8), 8); + set => this.gid = this.GetUIntTo8(value); + } + + public uint FileSize + { + get => Convert.ToUInt32(this.GetString(this.size, 12), 8); + set => this.size = this.GetUIntTo8(value, 12); + } + + public DateTimeOffset LastModified + { + get => DateTimeOffset.FromUnixTimeSeconds((long)Convert.ToUInt64(this.GetString(this.mtime, 12), 8)); + set => this.mtime = this.GetUIntTo8((uint)value.ToUnixTimeSeconds(), 12); + } + + public uint Checksum + { + get => Convert.ToUInt32(this.GetString(this.chksum, 8), 8); + set + { + // GNU tar does that, I have no idea why + var s = this.GetUIntTo8(value, 7); + var buffer = new byte[8]; + Array.Copy(s, buffer, 7); + buffer[7] = 32; + this.chksum = buffer; + } + } + + public TarTypeFlag TypeFlag + { + get => (TarTypeFlag)this.typeflag; + set => this.typeflag = (byte)value; + } + + public string LinkName + { + get => this.GetString(this.linkname, 100); + set => this.linkname = this.CreateString(value, 100); + } + + public string Magic + { + get => this.GetString(this.magic, 6)?.Trim(); + set => this.magic = this.CreateString(value.PadRight(6), 6); + } + + public uint? Version + { + get + { + var v = this.GetString(this.version, 2); + if (uint.TryParse(v, out uint rv)) + { + return rv; + } + + return null; + } + + set + { + if (value == null) + { + this.version = new byte[] { 32, 0 }; + } + else + { + this.version = this.GetUIntTo8(value, 2); + } + } + } + + public string UserName + { + get => this.GetString(this.uname, 32); + set => this.uname = this.CreateString(value, 32); + } + + public string GroupName + { + get => this.GetString(this.gname, 32); + set => this.gname = this.CreateString(value, 32); + } + + public uint? DevMajor + { + get => this.devmajor[0] == 0 ? (uint?)null : Convert.ToUInt32(this.GetString(this.devmajor, 8), 8); + set => this.devmajor = this.GetUIntTo8(value); + } + + public uint? DevMinor + { + get => this.devminor[0] == 0 ? (uint?)null : Convert.ToUInt32(this.GetString(this.devminor, 8), 8); + set => this.devminor = this.GetUIntTo8(value); + } + + public string Prefix + { + get => this.GetString(this.prefix, 155); + set => this.prefix = this.CreateString(value, 155); + } + + public uint ComputeChecksum() + { + var other = this; + other.chksum = new byte[8]; + for (var c = 0; c < 8; c++) + { + other.chksum[c] = 32; + } + + var data = new byte[Marshal.SizeOf()]; + fixed (byte* ptr = data) + { + Marshal.StructureToPtr(other, new IntPtr(ptr), true); + } + + uint sum = 0; + foreach (var b in data) + { + sum += b; + } + + return sum; + } + + private string GetString(byte[] data, int maxLen) + { + int len; + for (len = 0; len < maxLen; len++) + { + if (data[len] == 0) + { + break; + } + } + + if (len == 0) + { + return null; + } + + return Encoding.UTF8.GetString(data, 0, len); + } + + private byte[] GetUIntTo8(uint? data, int len = 8) + { + if (data == null) + { + return new byte[len]; + } + + return this.CreateString(Convert.ToString(data.Value, 8).PadLeft(len - 1, '0'), len); + } + + private byte[] CreateString(string s, int len) + { + var target = new byte[len]; + if (s == null) + { + return target; + } + + var buffer = Encoding.UTF8.GetBytes(s); + if (buffer.Length > len) + { + throw new Exception($"String {s} exceeds the limit of {len}"); + } + + for (var c = 0; c < len; c++) + { + target[c] = (c < buffer.Length) ? buffer[c] : (byte)0; + } + + return target; + } + } +} \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/IO/TarTypeFlag.cs b/DebUOS/Packaging.Targets/IO/TarTypeFlag.cs new file mode 100644 index 0000000..fc54f9f --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/TarTypeFlag.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Packaging.Targets.IO +{ + internal enum TarTypeFlag : byte + { + RegType = (byte)'0', + ARegType = (byte)'\0', + LnkType = (byte)'1', + SymType = (byte)'2', + ChrType = (byte)'3', + BlkType = (byte)'4', + DirType = (byte)'5', + FifoType = (byte)'6', + ConttType = (byte)'7', + ExtendedHeader = (byte)'x', + GlobalExtendedHeader = (byte)'g', + LongName = (byte)'L', // See https://www.gnu.org/software/tar/manual/html_node/Standard.html + LongLink = (byte)'K' // See https://www.gnu.org/software/tar/manual/html_node/Standard.html + } +} diff --git a/DebUOS/Packaging.Targets/IO/XZInputStream.cs b/DebUOS/Packaging.Targets/IO/XZInputStream.cs new file mode 100644 index 0000000..4a01554 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/XZInputStream.cs @@ -0,0 +1,333 @@ +/* + * The MIT License (MIT) + + * Copyright (c) 2015 Roman Belkov, Kirill Melentyev + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +namespace Packaging.Targets.IO +{ + /// + /// Represents a which can decompress xz-compressed data. + /// + public class XZInputStream : Stream + { + /// + /// The size of the buffer + /// + private const int BufSize = 512; + + private readonly List internalBuffer = new List(); + private readonly Stream innerStream; + private readonly IntPtr inbuf; + private readonly IntPtr outbuf; + private LzmaStream lzmaStream; + private long length; + private long position; + private bool disposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The underlying from which to decompress the data. + /// + public XZInputStream(Stream stream) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + this.innerStream = stream; + + var ret = NativeMethods.lzma_stream_decoder(ref this.lzmaStream, ulong.MaxValue, LzmaDecodeFlags.Concatenated); + + this.inbuf = Marshal.AllocHGlobal(BufSize); + this.outbuf = Marshal.AllocHGlobal(BufSize); + + this.lzmaStream.AvailIn = 0; + this.lzmaStream.NextOut = this.outbuf; + this.lzmaStream.AvailOut = BufSize; + + if (ret == LzmaResult.OK) + { + return; + } + + switch (ret) + { + case LzmaResult.MemError: + throw new Exception("Memory allocation failed"); + + case LzmaResult.OptionsError: + throw new Exception("Unsupported decompressor flags"); + + default: + throw new Exception("Unknown error, possibly a bug"); + } + } + + /// + public override bool CanRead + { + get + { + this.EnsureNotDisposed(); + return true; + } + } + + /// + public override bool CanSeek + { + get + { + this.EnsureNotDisposed(); + return false; + } + } + + /// + public override bool CanWrite + { + get + { + this.EnsureNotDisposed(); + return false; + } + } + + /// + public override long Length + { + get + { + this.EnsureNotDisposed(); + + const int streamFooterSize = 12; + + if (this.length == 0) + { + var lzmaStreamFlags = default(LzmaStreamFlags); + var streamFooter = new byte[streamFooterSize]; + + this.innerStream.Seek(-streamFooterSize, SeekOrigin.End); + this.innerStream.Read(streamFooter, 0, streamFooterSize); + + NativeMethods.lzma_stream_footer_decode(ref lzmaStreamFlags, streamFooter); + var indexPointer = new byte[lzmaStreamFlags.BackwardSize]; + + this.innerStream.Seek(-streamFooterSize - (long)lzmaStreamFlags.BackwardSize, SeekOrigin.End); + this.innerStream.Read(indexPointer, 0, (int)lzmaStreamFlags.BackwardSize); + this.innerStream.Seek(0, SeekOrigin.Begin); + + var index = IntPtr.Zero; + var memLimit = ulong.MaxValue; + uint inPos = 0; + + NativeMethods.lzma_index_buffer_decode(ref index, ref memLimit, IntPtr.Zero, indexPointer, ref inPos, lzmaStreamFlags.BackwardSize); + + if (inPos != lzmaStreamFlags.BackwardSize) + { + NativeMethods.lzma_index_end(index, IntPtr.Zero); + throw new Exception("Index decoding failed!"); + } + + var uSize = NativeMethods.lzma_index_uncompressed_size(index); + + NativeMethods.lzma_index_end(index, IntPtr.Zero); + this.length = (long)uSize; + return this.length; + } + else + { + return this.length; + } + } + } + + /// + public override long Position + { + get + { + this.EnsureNotDisposed(); + return this.position; + } + + set + { + this.EnsureNotDisposed(); + throw new NotSupportedException("XZ Stream does not support setting position"); + } + } + + /// + public override void Flush() + { + this.EnsureNotDisposed(); + + throw new NotSupportedException("XZ Stream does not support flush"); + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + this.EnsureNotDisposed(); + + throw new NotSupportedException("XZ Stream does not support seek"); + } + + /// + public override void SetLength(long value) + { + throw new NotSupportedException("XZ Stream does not support setting length"); + } + + /// + /// Reads bytes from stream + /// + /// byte read or -1 on end of stream + public override int Read(byte[] buffer, int offset, int count) + { + this.EnsureNotDisposed(); + + var action = LzmaAction.Run; + + var readBuf = new byte[BufSize]; + var outManagedBuf = new byte[BufSize]; + + while (this.internalBuffer.Count < count) + { + if (this.lzmaStream.AvailIn == 0) + { + this.lzmaStream.AvailIn = (uint)this.innerStream.Read(readBuf, 0, readBuf.Length); + Marshal.Copy(readBuf, 0, this.inbuf, (int)this.lzmaStream.AvailIn); + this.lzmaStream.NextIn = this.inbuf; + + if (this.lzmaStream.AvailIn == 0) + { + action = LzmaAction.Finish; + } + } + + var ret = NativeMethods.lzma_code(ref this.lzmaStream, action); + + if (this.lzmaStream.AvailOut == 0 || ret == LzmaResult.StreamEnd) + { + var writeSize = BufSize - (int)this.lzmaStream.AvailOut; + Marshal.Copy(this.outbuf, outManagedBuf, 0, writeSize); + + this.internalBuffer.AddRange(outManagedBuf); + var tail = outManagedBuf.Length - writeSize; + this.internalBuffer.RemoveRange(this.internalBuffer.Count - tail, tail); + + this.lzmaStream.NextOut = this.outbuf; + this.lzmaStream.AvailOut = BufSize; + } + + if (ret != LzmaResult.OK) + { + if (ret == LzmaResult.StreamEnd) + { + break; + } + + NativeMethods.lzma_end(ref this.lzmaStream); + + switch (ret) + { + case LzmaResult.MemError: + throw new Exception("Memory allocation failed"); + + case LzmaResult.FormatError: + throw new Exception("The input is not in the .xz format"); + + case LzmaResult.OptionsError: + throw new Exception("Unsupported compression options"); + + case LzmaResult.DataError: + throw new Exception("Compressed file is corrupt"); + + case LzmaResult.BufferError: + throw new Exception("Compressed file is truncated or otherwise corrupt"); + + default: + throw new Exception("Unknown error.Possibly a bug"); + } + } + } + + if (this.internalBuffer.Count >= count) + { + this.internalBuffer.CopyTo(0, buffer, offset, count); + this.internalBuffer.RemoveRange(0, count); + this.position += count; + return count; + } + else + { + var intBufLength = this.internalBuffer.Count; + this.internalBuffer.CopyTo(0, buffer, offset, intBufLength); + this.internalBuffer.Clear(); + this.position += intBufLength; + return intBufLength; + } + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("XZ Input stream does not support writing"); + } + + /// + protected override void Dispose(bool disposing) + { + if (this.disposed) + { + return; + } + + NativeMethods.lzma_end(ref this.lzmaStream); + + Marshal.FreeHGlobal(this.inbuf); + Marshal.FreeHGlobal(this.outbuf); + + base.Dispose(disposing); + + this.disposed = true; + } + + private void EnsureNotDisposed() + { + if (this.disposed) + { + throw new ObjectDisposedException(nameof(XZInputStream)); + } + } + } +} \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/IO/XZOutputStream.cs b/DebUOS/Packaging.Targets/IO/XZOutputStream.cs new file mode 100644 index 0000000..1e76991 --- /dev/null +++ b/DebUOS/Packaging.Targets/IO/XZOutputStream.cs @@ -0,0 +1,339 @@ +/* + * The MIT License (MIT) + + * Copyright (c) 2015 Roman Belkov, Kirill Melentyev + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ + +using System; +using System.Diagnostics; +using System.IO; + +namespace Packaging.Targets.IO +{ + public unsafe class XZOutputStream : Stream + { + /// + /// Default compression preset. + /// + public const uint DefaultPreset = 6; + public const uint PresetExtremeFlag = (uint)1 << 31; + + // You can tweak BufSize value to get optimal results + // of speed and chunk size + private const int BufSize = 4096; + + private readonly Stream innerStream; + private readonly bool leaveOpen; + private readonly byte[] outbuf; + private LzmaStream lzmaStream; + private bool disposed; + + public XZOutputStream(Stream s) + : this(s, DefaultThreads) + { + } + + public XZOutputStream(Stream s, int threads) + : this(s, threads, DefaultPreset) + { + } + + public XZOutputStream(Stream s, int threads, uint preset) + : this(s, threads, preset, false) + { + } + + public XZOutputStream(Stream s, int threads, uint preset, bool leaveOpen) + { + this.innerStream = s; + this.leaveOpen = leaveOpen; + + LzmaResult ret; + if (threads == 1 || !NativeMethods.SupportsMultiThreading) + { + ret = NativeMethods.lzma_easy_encoder(ref this.lzmaStream, preset, LzmaCheck.Crc64); + } + else + { + if (threads <= 0) + { + throw new ArgumentOutOfRangeException(nameof(threads)); + } + + if (threads > Environment.ProcessorCount) + { + Trace.TraceWarning("{0} threads required, but only {1} processors available", threads, Environment.ProcessorCount); + threads = Environment.ProcessorCount; + } + + var mt = new LzmaMT() + { + preset = preset, + check = LzmaCheck.Crc64, + threads = (uint)threads + }; + ret = NativeMethods.lzma_stream_encoder_mt(ref this.lzmaStream, ref mt); + } + + if (ret == LzmaResult.OK) + { + this.outbuf = new byte[BufSize]; + this.lzmaStream.AvailOut = BufSize; + return; + } + + GC.SuppressFinalize(this); + throw GetError(ret); + } + + ~XZOutputStream() + { + this.Dispose(false); + } + + public static int DefaultThreads => Environment.ProcessorCount; + + public static bool SupportsMultiThreading => NativeMethods.SupportsMultiThreading; + + /// + public override bool CanRead + { + get + { + this.EnsureNotDisposed(); + return false; + } + } + + /// + public override bool CanSeek + { + get + { + this.EnsureNotDisposed(); + return false; + } + } + + /// + public override bool CanWrite + { + get + { + this.EnsureNotDisposed(); + return true; + } + } + + /// + public override long Length + { + get + { + this.EnsureNotDisposed(); + throw new NotSupportedException(); + } + } + + /// + public override long Position + { + get + { + this.EnsureNotDisposed(); + throw new NotSupportedException(); + } + + set + { + this.EnsureNotDisposed(); + throw new NotSupportedException(); + } + } + + /// + /// Single-call buffer encoding + /// + public static byte[] Encode(byte[] buffer, uint preset = DefaultPreset) + { + var res = new byte[(long)NativeMethods.lzma_stream_buffer_bound((UIntPtr)buffer.Length)]; + + UIntPtr outPos; + var ret = NativeMethods.lzma_easy_buffer_encode(preset, LzmaCheck.Crc64, null, buffer, (UIntPtr)buffer.Length, res, &outPos, (UIntPtr)res.Length); + if (ret != LzmaResult.OK) + { + throw GetError(ret); + } + + if ((long)outPos < res.Length) + { + Array.Resize(ref res, (int)(ulong)outPos); + } + + return res; + } + + /// + public override void Flush() + { + throw new NotSupportedException(); + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + this.EnsureNotDisposed(); + throw new NotSupportedException(); + } + + /// + public override void SetLength(long value) + { + this.EnsureNotDisposed(); + throw new NotSupportedException(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + this.EnsureNotDisposed(); + throw new NotSupportedException(); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + this.EnsureNotDisposed(); + + if (count == 0) + { + return; + } + + var guard = buffer[checked((uint)offset + (uint)count) - 1]; + + if (this.lzmaStream.AvailIn != 0) + { + throw new InvalidOperationException(); + } + + this.lzmaStream.AvailIn = (uint)count; + do + { + LzmaResult ret; + fixed (byte* inbuf = &buffer[offset]) + { + this.lzmaStream.NextIn = (IntPtr)inbuf; + fixed (byte* outbuf = &this.outbuf[BufSize - this.lzmaStream.AvailOut]) + { + this.lzmaStream.NextOut = (IntPtr)outbuf; + ret = NativeMethods.lzma_code(ref this.lzmaStream, LzmaAction.Run); + } + + offset += (int)((ulong)this.lzmaStream.NextIn - (ulong)(IntPtr)inbuf); + } + + if (ret != LzmaResult.OK) + { + throw this.ThrowError(ret); + } + + if (this.lzmaStream.AvailOut == 0) + { + this.innerStream.Write(this.outbuf, 0, BufSize); + this.lzmaStream.AvailOut = BufSize; + } + } + while (this.lzmaStream.AvailIn != 0); + } + + /// + protected override void Dispose(bool disposing) + { + // finish encoding only if all input has been successfully processed + if (this.lzmaStream.InternalState != IntPtr.Zero && this.lzmaStream.AvailIn == 0) + { + LzmaResult ret; + do + { + fixed (byte* outbuf = &this.outbuf[BufSize - (int)this.lzmaStream.AvailOut]) + { + this.lzmaStream.NextOut = (IntPtr)outbuf; + ret = NativeMethods.lzma_code(ref this.lzmaStream, LzmaAction.Finish); + } + + if (ret > LzmaResult.StreamEnd) + { + throw this.ThrowError(ret); + } + + var writeSize = BufSize - (int)this.lzmaStream.AvailOut; + if (writeSize != 0) + { + this.innerStream.Write(this.outbuf, 0, writeSize); + this.lzmaStream.AvailOut = BufSize; + } + } + while (ret != LzmaResult.StreamEnd); + } + + NativeMethods.lzma_end(ref this.lzmaStream); + + if (disposing && !this.leaveOpen) + { + this.innerStream?.Dispose(); + } + + base.Dispose(disposing); + + this.disposed = true; + } + + private static Exception GetError(LzmaResult ret) + { + switch (ret) + { + case LzmaResult.MemError: return new OutOfMemoryException("Memory allocation failed"); + case LzmaResult.OptionsError: return new ArgumentException("Specified preset is not supported"); + case LzmaResult.UnsupportedCheck: return new Exception("Specified integrity check is not supported"); + case LzmaResult.DataError: return new InvalidDataException("File size limits exceeded"); + default: return new Exception("Unknown error, possibly a bug: " + ret); + } + } + + /// + /// Throws an exception if this stream is disposed of. + /// + private void EnsureNotDisposed() + { + if (this.disposed) + { + throw new ObjectDisposedException(nameof(XZOutputStream)); + } + } + + private Exception ThrowError(LzmaResult ret) + { + NativeMethods.lzma_end(ref this.lzmaStream); + return GetError(ret); + } + } +} \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/Native/FunctionLoader.cs b/DebUOS/Packaging.Targets/Native/FunctionLoader.cs new file mode 100644 index 0000000..1500691 --- /dev/null +++ b/DebUOS/Packaging.Targets/Native/FunctionLoader.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Packaging.Targets.Native +{ + /// + /// Supports loading functions from native libraries. Provides a more flexible alternative to P/Invoke. + /// + internal static class FunctionLoader + { + /// + /// Attempts to load a native library. + /// + /// + /// Possible names of the library on Windows. This can include full paths. + /// + /// + /// Possible names of the library on Linux. + /// + /// + /// Possible names of the library on macOS. + /// + /// + /// A handle to the library when found; otherwise, . + /// + public static IntPtr LoadNativeLibrary(IEnumerable windowsNames, IEnumerable linuxNames, IEnumerable osxNames) + { + IntPtr lib = IntPtr.Zero; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + foreach (var name in linuxNames) + { + lib = LinuxNativeMethods.dlopen(name, LinuxNativeMethods.RTLD_NOW); + + if (lib != IntPtr.Zero) + { + break; + } + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + foreach (var name in osxNames) + { + lib = MacNativeMethods.dlopen(name, MacNativeMethods.RTLD_NOW); + + if (lib != IntPtr.Zero) + { + break; + } + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + foreach (var name in windowsNames) + { + lib = WindowsNativeMethods.LoadLibrary(name); + + if (lib != IntPtr.Zero) + { + break; + } + } + } + else + { + throw new PlatformNotSupportedException(); + } + + // This function may return a null handle. If it does, individual functions loaded from it will throw a DllNotFoundException, + // but not until an attempt is made to actually use the function (rather than load it). This matches how PInvokes behave. + return lib; + } + + /// + /// Creates a delegate which invokes a native function. + /// + /// + /// The function delegate. + /// + /// + /// The native library which contains the function. + /// + /// + /// The name of the function for which to create the delegate. + /// + /// + /// A new delegate which points to the native function. + /// + public static T LoadFunctionDelegate(IntPtr nativeLibraryHandle, string functionName, bool throwOnError = true) + where T : class + { + IntPtr ptr = LoadFunctionPointer(nativeLibraryHandle, functionName); + + if (ptr == IntPtr.Zero) + { + if (throwOnError) + { +#if NETSTANDARD2_0 + throw new EntryPointNotFoundException($"Could not find the entrypoint for {functionName}"); +#else + throw new Exception($"Could not find the entrypoint for {functionName}"); +#endif + } + else + { + return null; + } + } + + return Marshal.GetDelegateForFunctionPointer(ptr); + } + + private static IntPtr LoadFunctionPointer(IntPtr nativeLibraryHandle, string functionName) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return LinuxNativeMethods.dlsym(nativeLibraryHandle, functionName); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return MacNativeMethods.dlsym(nativeLibraryHandle, functionName); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return WindowsNativeMethods.GetProcAddress(nativeLibraryHandle, functionName); + } + else + { + throw new PlatformNotSupportedException(); + } + } + } +} diff --git a/DebUOS/Packaging.Targets/Native/LinuxNativeMethods.cs b/DebUOS/Packaging.Targets/Native/LinuxNativeMethods.cs new file mode 100644 index 0000000..2870a41 --- /dev/null +++ b/DebUOS/Packaging.Targets/Native/LinuxNativeMethods.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace Packaging.Targets.Native +{ + internal static class LinuxNativeMethods + { + public const int RTLD_NOW = 0x002; + + private const string Libdl = "libdl.so.2"; + + [DllImport(Libdl)] + public static extern IntPtr dlsym(IntPtr handle, string symbol); + + [DllImport(Libdl)] + public static extern IntPtr dlopen(string fileName, int flag); + } +} diff --git a/DebUOS/Packaging.Targets/Native/MacNativeMethods.cs b/DebUOS/Packaging.Targets/Native/MacNativeMethods.cs new file mode 100644 index 0000000..ea1a706 --- /dev/null +++ b/DebUOS/Packaging.Targets/Native/MacNativeMethods.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace Packaging.Targets.Native +{ + internal static class MacNativeMethods + { + public const int RTLD_NOW = 0x002; + + private const string Libdl = "libdl"; + + [DllImport(Libdl)] + public static extern IntPtr dlsym(IntPtr handle, string symbol); + + [DllImport(Libdl)] + public static extern IntPtr dlopen(string fileName, int flag); + } +} diff --git a/DebUOS/Packaging.Targets/Native/WindowsNativeMethods.cs b/DebUOS/Packaging.Targets/Native/WindowsNativeMethods.cs new file mode 100644 index 0000000..3389a82 --- /dev/null +++ b/DebUOS/Packaging.Targets/Native/WindowsNativeMethods.cs @@ -0,0 +1,48 @@ +using System; +using System.Runtime.InteropServices; + +namespace Packaging.Targets.Native +{ + internal static class WindowsNativeMethods + { + private const string Kernel32 = "kernel32"; + + [DllImport(Kernel32, CharSet = CharSet.Ansi, BestFitMapping = false)] + public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); + + /// + /// Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded. + /// + /// + /// + /// The name of the module. This can be either a library module (a .dll file) or an executable module (an .exe file). + /// The name specified is the file name of the module and is not related to the name stored in the library module itself, + /// as specified by the LIBRARY keyword in the module-definition (.def) file. + /// + /// + /// If the string specifies a full path, the function searches only that path for the module. + /// + /// + /// If the string specifies a relative path or a module name without a path, the function uses a standard search strategy + /// to find the module; for more information, see the Remarks. + /// + /// + /// If the function cannot find the module, the function fails. When specifying a path, be sure to use backslashes (\), + /// not forward slashes (/). For more information about paths, see Naming a File or Directory. + /// + /// + /// If the string specifies a module name without a path and the file name extension is omitted, the function appends the + /// default library extension .dll to the module name. To prevent the function from appending .dll to the module name, + /// include a trailing point character (.) in the module name string. + /// + /// + /// + /// If the function succeeds, the return value is a handle to the module. + /// If the function fails, the return value is . To get extended error information, call + /// . + /// + /// + [DllImport(Kernel32, SetLastError = true)] + public static extern IntPtr LoadLibrary(string dllToLoad); + } +} diff --git a/DebUOS/Packaging.Targets/Packaging.Targets.csproj b/DebUOS/Packaging.Targets/Packaging.Targets.csproj new file mode 100644 index 0000000..91d5817 --- /dev/null +++ b/DebUOS/Packaging.Targets/Packaging.Targets.csproj @@ -0,0 +1,93 @@ + + + netstandard2.0 + This package supports the dotnet-pack and dotnet-zip packages. Once you've installed this package together with dotnet-zip or dotnet-tarball, you can run commands such as dotnet zip or dotnet tarball to generate .zip or .tar.gz archives which contain the published output of your project. + True + + + AnyCPU + + + + tools + + true + true + snupkg + true + MIT + true + + + + + all + + + all + + + + all + + + all + + + all + + + + + + true + build\ + + + + + true + tools\netstandard2.0\ + + + + + true + tools\netstandard2.0\ + + + + + true + runtimes\win7-x64\native\ + + + PreserveNewest + + + + + + dotnet-packaging.ruleset + + + + + + + + <!-- This file is auto-generated. Do not edit manually --> + <Project> + <PropertyGroup> + <PackagingNuGetVersion>$(NuGetPackageVersion)</PackagingNuGetVersion> + </PropertyGroup> + </Project> + + $(NuGetPackageVersion) + + + + + + diff --git a/DebUOS/Packaging.Targets/Rpm/ChangelogEntry.cs b/DebUOS/Packaging.Targets/Rpm/ChangelogEntry.cs new file mode 100644 index 0000000..8799cb7 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/ChangelogEntry.cs @@ -0,0 +1,69 @@ +using System; + +namespace Packaging.Targets.Rpm +{ + /// + /// Represents an entry in the package changelog. + /// + internal class ChangelogEntry + { + /// + /// Initializes a new instance of the class. + /// + public ChangelogEntry() + { + } + + /// + /// Initializes a new instance of the class, and propopulates the fields. + /// + /// + /// The date at which the entry was created. + /// + /// + /// The name and e-mail address of the author. + /// + /// + /// A description fo the entry. + /// + public ChangelogEntry(DateTimeOffset date, string name, string text) + { + this.Date = date; + this.Name = name; + this.Text = text; + } + + /// + /// Gets or sets the date at which the entry was created. + /// + public DateTimeOffset Date + { + get; + set; + } + + /// + /// Gets the name and e-mail address of the author. + /// + public string Name + { + get; + set; + } + + /// + /// Gets or sets a description of the entry. + /// + public string Text + { + get; + set; + } + + /// + public override string ToString() + { + return $"{this.Date} {this.Name}: {this.Text}"; + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/CollectionExtensions.cs b/DebUOS/Packaging.Targets/Rpm/CollectionExtensions.cs new file mode 100644 index 0000000..a1320ea --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/CollectionExtensions.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Packaging.Targets.Rpm +{ + /// + /// Provides extension methods for the class. + /// + internal static class CollectionExtensions + { + /// + /// Adds multiple values at once. + /// + /// + /// The type of the values. + /// + /// + /// The collection to which to add the values. + /// + /// + /// The values to add. + /// + public static void AddRange(this Collection collection, IEnumerable values) + { + foreach (var v in values) + { + collection.Add(v); + } + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/DefaultOrder.cs b/DebUOS/Packaging.Targets/Rpm/DefaultOrder.cs new file mode 100644 index 0000000..11f7d8e --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/DefaultOrder.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; + +namespace Packaging.Targets.Rpm +{ + /// + /// Specifies the default order in which tags are saved in the various sections. Used mainly to maintain binary compatibility + /// with some test files. + /// + internal class DefaultOrder + { + /// + /// Gets the default order in which the tags are saved in the header section. The order depends on the integer value + /// of the tag, in ascending order. RPM is very finicky about this order - if you do not respect it, the RPM package + /// will almost certainly be rejected which messages such as "headerRead failed: hdr load: BAD". + /// + public static List Header + { + get + { + return new List() + { + IndexTag.RPMTAG_HEADERIMMUTABLE, + IndexTag.RPMTAG_HEADERI18NTABLE, + IndexTag.RPMTAG_NAME, + IndexTag.RPMTAG_VERSION, + IndexTag.RPMTAG_RELEASE, + IndexTag.RPMTAG_SUMMARY, + IndexTag.RPMTAG_DESCRIPTION, + IndexTag.RPMTAG_BUILDTIME, + IndexTag.RPMTAG_BUILDHOST, + IndexTag.RPMTAG_SIZE, + IndexTag.RPMTAG_DISTRIBUTION, + IndexTag.RPMTAG_VENDOR, + IndexTag.RPMTAG_LICENSE, + IndexTag.RPMTAG_GROUP, + IndexTag.RPMTAG_URL, + IndexTag.RPMTAG_OS, + IndexTag.RPMTAG_ARCH, + IndexTag.RPMTAG_PREIN, + IndexTag.RPMTAG_POSTIN, + IndexTag.RPMTAG_PREUN, + IndexTag.RPMTAG_POSTUN, + IndexTag.RPMTAG_FILESIZES, + IndexTag.RPMTAG_FILEMODES, + IndexTag.RPMTAG_FILERDEVS, + IndexTag.RPMTAG_FILEMTIMES, + IndexTag.RPMTAG_FILEDIGESTS, + IndexTag.RPMTAG_FILELINKTOS, + IndexTag.RPMTAG_FILEFLAGS, + IndexTag.RPMTAG_FILEUSERNAME, + IndexTag.RPMTAG_FILEGROUPNAME, + IndexTag.RPMTAG_SOURCERPM, + IndexTag.RPMTAG_FILEVERIFYFLAGS, + IndexTag.RPMTAG_PROVIDENAME, + IndexTag.RPMTAG_REQUIREFLAGS, + IndexTag.RPMTAG_REQUIRENAME, + IndexTag.RPMTAG_REQUIREVERSION, + IndexTag.RPMTAG_RPMVERSION, + IndexTag.RPMTAG_CHANGELOGTIME, + IndexTag.RPMTAG_CHANGELOGNAME, + IndexTag.RPMTAG_CHANGELOGTEXT, + IndexTag.RPMTAG_PREINPROG, + IndexTag.RPMTAG_POSTINPROG, + IndexTag.RPMTAG_PREUNPROG, + IndexTag.RPMTAG_POSTUNPROG, + IndexTag.RPMTAG_COOKIE, + IndexTag.RPMTAG_FILEDEVICES, + IndexTag.RPMTAG_FILEINODES, + IndexTag.RPMTAG_FILELANGS, + IndexTag.RPMTAG_PROVIDEFLAGS, + IndexTag.RPMTAG_PROVIDEVERSION, + IndexTag.RPMTAG_DIRINDEXES, + IndexTag.RPMTAG_BASENAMES, + IndexTag.RPMTAG_DIRNAMES, + IndexTag.RPMTAG_OPTFLAGS, + IndexTag.RPMTAG_DISTURL, + IndexTag.RPMTAG_PAYLOADFORMAT, + IndexTag.RPMTAG_PAYLOADCOMPRESSOR, + IndexTag.RPMTAG_PAYLOADFLAGS, + IndexTag.RPMTAG_PLATFORM, + IndexTag.RPMTAG_FILECOLORS, + IndexTag.RPMTAG_FILECLASS, + IndexTag.RPMTAG_CLASSDICT, + IndexTag.RPMTAG_FILEDEPENDSX, + IndexTag.RPMTAG_FILEDEPENDSN, + IndexTag.RPMTAG_DEPENDSDICT, + IndexTag.RPMTAG_SOURCEPKGID, + IndexTag.RPMTAG_FILEDIGESTALGO, + }; + } + } + + /// + /// Gets the default order in which tags are saved in the signature sectin. + /// + public static List Signature + { + get + { + return new List() + { + SignatureTag.RPMTAG_HEADERSIGNATURES, + SignatureTag.RPMSIGTAG_RSA, + SignatureTag.RPMSIGTAG_SHA1, + SignatureTag.RPMSIGTAG_SIZE, + SignatureTag.RPMSIGTAG_PGP, + SignatureTag.RPMSIGTAG_MD5, + SignatureTag.RPMSIGTAG_PAYLOADSIZE + }; + } + } + } +} \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/Rpm/DependencyAttribute.cs b/DebUOS/Packaging.Targets/Rpm/DependencyAttribute.cs new file mode 100644 index 0000000..21c8ff5 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/DependencyAttribute.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Packaging.Targets.Rpm +{ + internal enum DependencyAttribute + { + RPMSENSE_LESS = 0x02, + RPMSENSE_GREATER = 0x04, + RPMSENSE_EQUAL = 0x08, + RPMSENSE_PREREQ = 0x40, + RPMSENSE_INTERP = 0x100, + RPMSENSE_SCRIPT_PRE = 0x200, + RPMSENSE_SCRIPT_POST = 0x400, + RPMSENSE_SCRIPT_PREUN = 0x800, + RPMSENSE_SCRIPT_POSTUN = 0x1000, + RPMSENSE_RPMLIB = 0x1000000 + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/ElfClass.cs b/DebUOS/Packaging.Targets/Rpm/ElfClass.cs new file mode 100644 index 0000000..e4ef779 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/ElfClass.cs @@ -0,0 +1,18 @@ +namespace Packaging.Targets.Rpm +{ + /// + /// Specifies whether an ELF file is 32-bit or 64-bit. + /// + internal enum ElfClass : byte + { + /// + /// The file is 32-bit. + /// + Elf32 = 1, + + /// + /// The file is 64-bit. + /// + Elf64 = 2 + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/ElfFile.cs b/DebUOS/Packaging.Targets/Rpm/ElfFile.cs new file mode 100644 index 0000000..84207a3 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/ElfFile.cs @@ -0,0 +1,52 @@ +using System; + +namespace Packaging.Targets.Rpm +{ + /// + /// Provides methods for working with ELF files. + /// + internal static class ElfFile + { + /// + /// Determines whether a file is an ELF file or not. + /// + /// + /// An array containing at least the first 4 bytes of the file. + /// + /// + /// if the file is an ELF file; otherwise, . + /// + internal static bool IsElfFile(byte[] header) + { + return header.Length > 4 && header[0] == 0x7f && header[1] == 0x45 && header[2] == 0x4c && header[3] == 0x46; + } + + /// + /// Reads the of an ELF file. + /// + /// + /// An array containing at least the first 0x14 bytes of the file. + /// + /// + /// A object represening the ELF file header. + /// + internal static ElfHeader ReadHeader(byte[] header) + { + if (!IsElfFile(header)) + { + throw new InvalidOperationException(); + } + + ElfHeader value = default(ElfHeader); + value.@class = (ElfClass)header[4]; + value.data = header[5]; + value.version = header[6]; + value.osAbi = header[7]; + value.abiVersion = header[8]; + value.type = (ElfType)BitConverter.ToInt16(header, 0x10); + value.machine = (ElfMachine)BitConverter.ToInt16(header, 0x12); + + return value; + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/ElfHeader.cs b/DebUOS/Packaging.Targets/Rpm/ElfHeader.cs new file mode 100644 index 0000000..a5c9083 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/ElfHeader.cs @@ -0,0 +1,46 @@ +namespace Packaging.Targets.Rpm +{ +#pragma warning disable SA1307 // Accessible fields must begin with upper-case letter + /// + /// Represents the first fields of a header of an ELF file. + /// + /// + internal struct ElfHeader + { + /// + /// The . Indicates whether this is 64 or 32-bit. + /// + public ElfClass @class; + + /// + /// Set to 1 or 2 to indicate the endianness of the file. + /// + public byte data; + + /// + /// Should always be 1 to indicate the original ELF file format. + /// + public byte version; + + /// + /// Identifies the target operating system ABI. + /// + public byte osAbi; + + /// + /// Further specifies the ABI version. Its interpretation depends on the target ABI. + /// + public byte abiVersion; + + /// + /// Indicates whether the object is relocatable, executable, shared, or core. + /// + public ElfType type; + + /// + /// Specifies target instruction set architecture. + /// + public ElfMachine machine; + } +#pragma warning restore SA1307 // Accessible fields must begin with upper-case letter +} diff --git a/DebUOS/Packaging.Targets/Rpm/ElfMachine.cs b/DebUOS/Packaging.Targets/Rpm/ElfMachine.cs new file mode 100644 index 0000000..7d9fe09 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/ElfMachine.cs @@ -0,0 +1,65 @@ +namespace Packaging.Targets.Rpm +{ +#pragma warning disable SA1300 // Element must begin with upper-case letter + /// + /// Specifies target instruction set architecture. + /// + internal enum ElfMachine + { + /// + /// The file is generic. + /// + Generic = 0, + + /// + /// The file targets the SPARC architecture. + /// + SPARC = 2, + + /// + /// The file targets the x64 architecture. + /// + x64 = 3, + + /// + /// The file targets the MIPS architecture. + /// + MIPS = 8, + + /// + /// The file targets the PowerPC architecture. + /// + PowerPC = 0x14, + + /// + /// The file targets the ARM architecture. + /// + ARM = 0x28, + + /// + /// The file targets the SuperH architecture. + /// + SuperH = 0x2A, + + /// + /// THe file targets the IA-64 architecture. + /// + IA64 = 0x32, + + /// + /// The file targets the x86-64 architecture. + /// + x8664 = 0x3E, + + /// + /// The file targets the AArch64 (ARM64) architecture. + /// + AArch64 = 0xb7, + + /// + /// The file targets the RISC V architecture. + /// + RiscV = 0xF3 + } +#pragma warning restore SA1300 // Element must begin with upper-case letter +} diff --git a/DebUOS/Packaging.Targets/Rpm/ElfType.cs b/DebUOS/Packaging.Targets/Rpm/ElfType.cs new file mode 100644 index 0000000..bc1fe5b --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/ElfType.cs @@ -0,0 +1,28 @@ +namespace Packaging.Targets.Rpm +{ + /// + /// Represents the different types of ELF files. + /// + internal enum ElfType : ushort + { + /// + /// The file is relocatable. + /// + Relocatable = 1, + + /// + /// The file is executable. + /// + Executable = 2, + + /// + /// The file is shared. + /// + Shared = 3, + + /// + /// The file is core. + /// + Core = 4 + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/FileAnalyzer.cs b/DebUOS/Packaging.Targets/Rpm/FileAnalyzer.cs new file mode 100644 index 0000000..70f5f03 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/FileAnalyzer.cs @@ -0,0 +1,131 @@ +using Packaging.Targets.IO; +using System; +using System.Collections.ObjectModel; +using System.IO; + +namespace Packaging.Targets.Rpm +{ + /// + /// Provides a very basic implementation of the class. To get the details that are consistent + /// with those found in real-world RPM packages, more complete parsing of ELF files is required to get the full list of + /// dependencies, as well as the class (description). + /// + internal class FileAnalyzer : IFileAnalyzer + { + /// + public virtual Collection DetermineRequires(ArchiveEntry entry) + { + // For now, report no dependencies at all. Could be enhanced if ELF parsing is available. + var dependencies = new Collection(); + + return dependencies; + } + + /// + public virtual Collection DetermineProvides(ArchiveEntry entry) + { + // For now, report no provides at all. Could be enhanced if ELF parsing is available. + var dependencies = new Collection(); + + return dependencies; + } + + /// + public virtual RpmFileFlags DetermineFlags(ArchiveEntry entry) + { + // The only custom flags which are supported for now are the RPMFILE_DOC flags for non-executable + // files. + if (entry.Mode.HasFlag(LinuxFileMode.S_IFDIR)) + { + return RpmFileFlags.None; + } + else if (entry.Mode.HasFlag(LinuxFileMode.S_IFLNK)) + { + return RpmFileFlags.None; + } + else if (entry.TargetPath.StartsWith("/usr/share/doc")) + { + return RpmFileFlags.RPMFILE_DOC; + } + else + { + return RpmFileFlags.None; + } + } + + /// + public virtual RpmFileColor DetermineColor(ArchiveEntry entry) + { + // Only support ELF32 and ELF64 + switch (entry.Type) + { + case ArchiveEntryType.Executable32: + return RpmFileColor.RPMFC_ELF32; + + case ArchiveEntryType.Executable64: + return RpmFileColor.RPMFC_ELF64; + + default: + return RpmFileColor.RPMFC_BLACK; + } + } + + /// + public virtual bool IsExecutable(ArchiveEntry entry) + { + throw new NotSupportedException(); + } + + /// + public virtual string DetermineClass(ArchiveEntry entry) + { + // Very simplistic implementation - non-executable files are considered to be tet files. + if (entry.Mode.HasFlag(LinuxFileMode.S_IFDIR)) + { + return "directory"; + } + + if (entry.Mode.HasFlag(LinuxFileMode.S_IFLNK)) + { + return string.Empty; + } + + if (entry.TargetPath.EndsWith(".svg")) + { + return "SVG Scalable Vector Graphics image"; + } + else if (entry.TargetPath.EndsWith(".ttf")) + { + return "TrueType font data"; + } + else if (entry.TargetPath.EndsWith(".woff")) + { + return string.Empty; + } + else if (entry.TargetPath.EndsWith(".woff2")) + { + return string.Empty; + } + else if (entry.TargetPath.EndsWith(".eot")) + { + return string.Empty; + } + + if (!entry.Mode.HasFlag(LinuxFileMode.S_IXGRP) + && !entry.Mode.HasFlag(LinuxFileMode.S_IXOTH) + && !entry.Mode.HasFlag(LinuxFileMode.S_IXUSR)) + { + if (entry.IsAscii) + { + return "ASCII text"; + } + else + { + return "UTF-8 Unicode text"; + } + } + + return string.Empty; + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/IFileAnalyzer.cs b/DebUOS/Packaging.Targets/Rpm/IFileAnalyzer.cs new file mode 100644 index 0000000..170f2e5 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/IFileAnalyzer.cs @@ -0,0 +1,68 @@ +using Packaging.Targets.IO; +using System.Collections.ObjectModel; + +namespace Packaging.Targets.Rpm +{ + /// + /// Provides a common interface for classes which are able to analyze the contents of a file and provide metadata for that file. + /// + internal interface IFileAnalyzer + { + /// + /// Gets the which apply to this file. + /// + /// + /// The entry to analyze. + /// + /// The for the file. + /// + RpmFileFlags DetermineFlags(ArchiveEntry entry); + + /// + /// Gets the dependencies for this file. + /// + /// + /// The entry to analyze. + /// + /// + /// The dependencies for the file. + /// + Collection DetermineRequires(ArchiveEntry entry); + + /// + /// Gets the dependencies fulfilled by this file. + /// + /// + /// The entry to analyze. + /// + /// + /// The dependencies fulfilled by the file. + /// + Collection DetermineProvides(ArchiveEntry entry); + + /// + /// Gets the for this file. + /// + /// + /// The entry to analyze. + /// tes of the file to analyze. + /// + /// + /// The for the file. + /// + RpmFileColor DetermineColor(ArchiveEntry entry); + + /// + /// Gets the class of this file. + /// + /// + /// The entry to analyze. + /// + /// + /// The class of this file. + /// + string DetermineClass(ArchiveEntry entry); + + bool IsExecutable(ArchiveEntry entry); + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/IPackageSigner.cs b/DebUOS/Packaging.Targets/Rpm/IPackageSigner.cs new file mode 100644 index 0000000..08ee9df --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/IPackageSigner.cs @@ -0,0 +1,13 @@ +using Org.BouncyCastle.Bcpg.OpenPgp; +using System.IO; + +namespace Packaging.Targets.Rpm +{ + /// + /// Common interface for any class that can generate PGP signatures of a . + /// + internal interface IPackageSigner + { + PgpSignature Sign(Stream payload); + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/IndexHeader.cs b/DebUOS/Packaging.Targets/Rpm/IndexHeader.cs new file mode 100644 index 0000000..b5d1dfd --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/IndexHeader.cs @@ -0,0 +1,29 @@ +using System; + +namespace Packaging.Targets.Rpm +{ + internal struct IndexHeader + { + /// + /// Value identifying the purpose of the data associated with this Index Record. + /// The value of this field is dependent on the context in which the Index Record is used. + /// + public uint Tag; + + /// + /// Value identifying the type of the data associated with this Index Record. + /// + public IndexType Type; + + /// + /// Location in the Store of the data associated with this Index Record. This value should between 0 and the value contained + /// in . + /// + public int Offset; + + /// + /// Size of the data associated with this Index Record. The count is the number of elements whose size is defined by the type of this Record. + /// + public int Count; + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/IndexRecord.cs b/DebUOS/Packaging.Targets/Rpm/IndexRecord.cs new file mode 100644 index 0000000..13c3f34 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/IndexRecord.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Packaging.Targets.Rpm +{ + internal class IndexRecord + { + public IndexHeader Header + { + get; + set; + } + + public object Value + { + get; + set; + } + + public override string ToString() + { + return $"{this.Value} ({this.Header.Type})"; + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/IndexTag.cs b/DebUOS/Packaging.Targets/Rpm/IndexTag.cs new file mode 100644 index 0000000..a4052cb --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/IndexTag.cs @@ -0,0 +1,665 @@ +namespace Packaging.Targets.Rpm +{ + /// + internal enum IndexTag + { + /// + /// Unknown tag + /// + RPMTAG_NOT_FOUND = -1, + + RPMTAG_HEADERIMAGE = 61, + + /// + /// The signature tag differentiates a signature header from a metadata header, and identifies the original contents of the signature header. + /// + RPMTAG_HEADERSIGNATURES = 62, + + /// + /// This tag contains an index record which specifies the portion of the Header Record which was used for the calculation of a signature. + /// This data shall be preserved or any header-only signature will be invalidated. + /// + RPMTAG_HEADERIMMUTABLE = 63, + + RPMTAG_HEADERREGIONS = 64, + + /// + /// Contains a list of locales for which strings are provided in other parts of the package. + /// + RPMTAG_HEADERI18NTABLE = 100, + + // Header tags + /// + /// This tag specifies the name of the package. + /// + RPMTAG_NAME = 1000, + + /// + /// This tag specifies the version of the package. + /// + RPMTAG_VERSION = 1001, + + /// + /// This tag specifies the release of the package. + /// + RPMTAG_RELEASE = 1002, + + RPMTAG_EPOCH = 1003, + + /// + /// This tag specifies the summary description of the package. The summary value pointed to by this index record contains a one line description of the package. + /// + RPMTAG_SUMMARY = 1004, + + /// + /// This tag specifies the description of the package. The description value pointed to by this index record contains a full desription of the package. + /// + RPMTAG_DESCRIPTION = 1005, + + RPMTAG_BUILDTIME = 1006, + + RPMTAG_BUILDHOST = 1007, + + RPMTAG_INSTALLTIME = 1008, + + /// + /// This tag specifies the sum of the sizes of the regular files in the archive. + /// + RPMTAG_SIZE = 1009, + + /// + /// A string containing the name of the distribution on which the package was built. + /// + RPMTAG_DISTRIBUTION = 1010, + + /// + /// A string containing the name of the organization that produced the package. + /// + RPMTAG_VENDOR = 1011, + + RPMTAG_GIF = 1012, + + RPMTAG_XPM = 1013, + + /// + /// This tag specifies the license which applies to this package. + /// + RPMTAG_LICENSE = 1014, + + /// + /// A string identifying the tool used to build the package. + /// + RPMTAG_PACKAGER = 1015, + + /// + /// This tag specifies the administrative group to which this package belongs. + /// + RPMTAG_GROUP = 1016, + + RPMTAG_CHANGELOG = 1017, + + RPMTAG_SOURCE = 1018, + + RPMTAG_PATCH = 1019, + + /// + /// Generic package information URL. + /// + RPMTAG_URL = 1020, + + /// + /// This tag specifies the OS of the package. The OS value pointed to by this index record shall be "linux". + /// + RPMTAG_OS = 1021, + + /// + /// This tag specifies the architecture of the package. The architecture value pointed to by this index record is defined in architecture specific LSB specification. + /// + RPMTAG_ARCH = 1022, + + /// + /// Scripts which run before installing the package. + /// + RPMTAG_PREIN = 1023, + + /// + /// Scripts which run after installing the package. + /// + RPMTAG_POSTIN = 1024, + + /// + /// Scripts which run before uninstalling the package. + /// + RPMTAG_PREUN = 1025, + + /// + /// Scripts which run after uninstalling the package. + /// + RPMTAG_POSTUN = 1026, + + /// + /// This tag specifies the filenames when not in a compressed format as determined by the absence of rpmlib(CompressedFileNames) in the RPMTAG_REQUIRENAME index. + /// + RPMTAG_OLDFILENAMES = 1027, + + /// + /// This tag specifies the size of each file in the archive. + /// + RPMTAG_FILESIZES = 1028, + + RPMTAG_FILESTATES = 1029, + + /// + /// This tag specifies the mode of each file in the archive. + /// + RPMTAG_FILEMODES = 1030, + + RPMTAG_FILEUIDS = 1031, + + RPMTAG_FILEGIDS = 1032, + + /// + /// This tag specifies the device number from which the file was copied. + /// + RPMTAG_FILERDEVS = 1033, + + /// + /// This tag specifies the modification time in seconds since the epoch of each file in the archive. + /// + RPMTAG_FILEMTIMES = 1034, + + /// + /// This tag specifies the ASCII representation of the MD5 sum of the corresponding file contents. This value is empty if the corresponding archive entry is not a regular file. + /// + /// + RPMTAG_FILEDIGESTS = 1035, + + /// + /// The target for a symlink, otherwise NULL. + /// + RPMTAG_FILELINKTOS = 1036, + + /// + /// This tag specifies the bit(s) to classify and control how files are to be installed. See below. + /// + RPMTAG_FILEFLAGS = 1037, + + RPMTAG_ROOT = 1038, + + /// + /// This tag specifies the owner of the corresponding file. + /// + RPMTAG_FILEUSERNAME = 1039, + + /// + /// This tag specifies the group of the corresponding file. + /// + RPMTAG_FILEGROUPNAME = 1040, + + RPMTAG_EXCLUDE = 1041, + + RPMTAG_EXCLUSIVE = 1042, + + RPMTAG_ICON = 1043, + + /// + /// This tag specifies the name of the source RPM. + /// + RPMTAG_SOURCERPM = 1044, + + /// + /// This tag specifies the bit(s) to control how files are to be verified after install, specifying which checks should be performed. + /// + RPMTAG_FILEVERIFYFLAGS = 1045, + + /// + /// This tag specifies the uncompressed size of the Payload archive, including the cpio headers. + /// + RPMTAG_ARCHIVESIZE = 1046, + + /// + /// This tag indicates the name of the dependency provided by this package. + /// + RPMTAG_PROVIDENAME = 1047, + + /// + /// Bits(s) to specify the dependency range and context. + /// + RPMTAG_REQUIREFLAGS = 1048, + + /// + /// This tag indicates the dependencies for this package. + /// + RPMTAG_REQUIRENAME = 1049, + + /// + /// This tag indicates the versions associated with the values found in the RPMTAG_REQUIRENAME Index. + /// + RPMTAG_REQUIREVERSION = 1050, + + RPMTAG_NOSOURCE = 1051, + + RPMTAG_NOPATCH = 1052, + + /// + /// Bits(s) to specify the conflict range and context. + /// + RPMTAG_CONFLICTFLAGS = 1053, + + /// + /// This tag indicates the conflicting dependencies for this package. + /// + RPMTAG_CONFLICTNAME = 1054, + + /// + /// This tag indicates the versions associated with the values found in the RPMTAG_CONFLICTNAME Index. + /// + RPMTAG_CONFLICTVERSION = 1055, + + RPMTAG_DEFAULTPREFIX = 1056, + + RPMTAG_BUILDROOT = 1057, + + RPMTAG_INSTALLPREFIX = 1058, + + RPMTAG_EXCLUDEARCH = 1059, + + RPMTAG_EXCLUDEOS = 1060, + + RPMTAG_EXCLUSIVEARCH = 1061, + + RPMTAG_EXCLUSIVEOS = 1062, + + RPMTAG_AUTOREQPROV = 1063, + + /// + /// This tag indicates the version of RPM tool used to build this package. The value is unused. + /// + RPMTAG_RPMVERSION = 1064, + + RPMTAG_TRIGGERSCRIPTS = 1065, + + RPMTAG_TRIGGERNAME = 1066, + + RPMTAG_TRIGGERVERSION = 1067, + + RPMTAG_TRIGGERFLAGS = 1068, + + RPMTAG_TRIGGERINDEX = 1069, + + RPMTAG_VERIFYSCRIPT = 1079, + + /// + /// This tag specifies the Unix time in seconds since the epoch associated with each entry in the Changelog file. + /// + RPMTAG_CHANGELOGTIME = 1080, + + /// + /// This tag specifies the name of who made a change to this package. + /// + RPMTAG_CHANGELOGNAME = 1081, + + /// + /// This tag specifies the changes asssociated with a changelog entry. + /// + RPMTAG_CHANGELOGTEXT = 1082, + + RPMTAG_BROKENMD5 = 1083, + + RPMTAG_PREREQ = 1084, + + /// + /// The program which launches the pre-install script. + /// + RPMTAG_PREINPROG = 1085, + + /// + /// The program which launches the post-install script. + /// + RPMTAG_POSTINPROG = 1086, + + /// + /// The program which launches the pre-removal script. + /// + RPMTAG_PREUNPROG = 1087, + + /// + /// The program which launches the post-removal script. + /// + RPMTAG_POSTUNPROG = 1088, + + RPMTAG_BUILDARCHS = 1089, + + /// + /// This tag indicates the obsoleted dependencies for this package. + /// + RPMTAG_OBSOLETENAME = 1090, + + RPMTAG_VERIFYSCRIPTPROG = 1091, + + RPMTAG_TRIGGERSCRIPTPROG = 1092, + + RPMTAG_DOCDIR = 1093, + + /// + /// This tag contains an opaque string whose contents are undefined. + /// + RPMTAG_COOKIE = 1094, + + /// + /// This tag specifies the 16 bit device number from which the file was copied. + /// + RPMTAG_FILEDEVICES = 1095, + + /// + /// This tag specifies the inode value from the original file system on the the system on which it was built. + /// + RPMTAG_FILEINODES = 1096, + + /// + /// This tag specifies a per-file locale marker used to install only locale specific subsets of files when the package is installed. + /// + RPMTAG_FILELANGS = 1097, + RPMTAG_PREFIXES = 1098, + RPMTAG_INSTPREFIXES = 1099, + RPMTAG_TRIGGERIN = 1100, + RPMTAG_TRIGGERUN = 1101, + RPMTAG_TRIGGERPOSTUN = 1102, + RPMTAG_AUTOREQ = 1103, + RPMTAG_AUTOPROV = 1104, + RPMTAG_CAPABILITY = 1105, + RPMTAG_SOURCEPACKAGE = 1106, + RPMTAG_OLDORIGFILENAMES = 1107, + RPMTAG_BUILDPREREQ = 1108, + RPMTAG_BUILDREQUIRES = 1109, + RPMTAG_BUILDCONFLICTS = 1110, + RPMTAG_BUILDMACROS = 1111, + + /// + /// Bits(s) to specify the conflict range and context. + /// + RPMTAG_PROVIDEFLAGS = 1112, + + /// + /// This tag indicates the versions associated with the values found in the RPMTAG_PROVIDENAME Index. + /// + RPMTAG_PROVIDEVERSION = 1113, + + /// + /// Bits(s) to specify the conflict range and context. + /// + RPMTAG_OBSOLETEFLAGS = 1114, + + /// + /// This tag indicates the versions associated with the values found in the RPMTAG_OBSOLETENAME Index. + /// + RPMTAG_OBSOLETEVERSION = 1115, + + /// + /// This tag specifies the index into the array provided by the RPMTAG_DIRNAMES Index which contains the directory name for the corresponding filename. + /// + RPMTAG_DIRINDEXES = 1116, + + /// + /// This tag specifies the base portion of the corresponding filename. + /// + RPMTAG_BASENAMES = 1117, + + /// + /// One of RPMTAG_OLDFILENAMES or the tuple RPMTAG_DIRINDEXES,RPMTAG_BASENAMES,RPMTAG_DIRNAMES shall be present, but not both. + /// + RPMTAG_DIRNAMES = 1118, + RPMTAG_ORIGDIRINDEXES = 1119, + RPMTAG_ORIGBASENAMES = 1120, + RPMTAG_ORIGDIRNAMES = 1121, + + /// + /// This tag indicates additional flags which may have been passed to the compiler when building this package. + /// + RPMTAG_OPTFLAGS = 1122, + + /// + /// URL for package. + /// + RPMTAG_DISTURL = 1123, + + /// + /// This tag specifies the format of the Archive section. The format value pointed to by this index record shall be 'cpio'. + /// + RPMTAG_PAYLOADFORMAT = 1124, + + /// + /// This tag specifies the compression used on the Archive section. The compression value pointed to by this index record shall be 'gzip'. + /// + RPMTAG_PAYLOADCOMPRESSOR = 1125, + + /// + /// This tag indicates the compression level used for the Payload. This value shall always be '9'. + /// + RPMTAG_PAYLOADFLAGS = 1126, + RPMTAG_INSTALLCOLOR = 1127, + RPMTAG_INSTALLTID = 1128, + RPMTAG_REMOVETID = 1129, + RPMTAG_SHA1RHN = 1130, + + /// + /// This tag contains an opaque string whose contents are undefined. + /// + RPMTAG_RHNPLATFORM = 1131, + + /// + /// This tag contains an opaque string whose contents are undefined. + /// + RPMTAG_PLATFORM = 1132, + + RPMTAG_PATCHESNAME = 1133, + RPMTAG_PATCHESFLAGS = 1134, + RPMTAG_PATCHESVERSION = 1135, + RPMTAG_CACHECTIME = 1136, + RPMTAG_CACHEPKGPATH = 1137, + RPMTAG_CACHEPKGSIZE = 1138, + RPMTAG_CACHEPKGMTIME = 1139, + RPMTAG_FILECOLORS = 1140, + RPMTAG_FILECLASS = 1141, + RPMTAG_CLASSDICT = 1142, + RPMTAG_FILEDEPENDSX = 1143, + RPMTAG_FILEDEPENDSN = 1144, + RPMTAG_DEPENDSDICT = 1145, + RPMTAG_SOURCEPKGID = 1146, + RPMTAG_FILECONTEXTS = 1147, + RPMTAG_FSCONTEXTS = 1148, + RPMTAG_RECONTEXTS = 1149, + RPMTAG_POLICIES = 1150, + RPMTAG_PRETRANS = 1151, + RPMTAG_POSTTRANS = 1152, + RPMTAG_PRETRANSPROG = 1153, + RPMTAG_POSTTRANSPROG = 1154, + RPMTAG_DISTTAG = 1155, + RPMTAG_OLDSUGGESTSNAME = 1156, + RPMTAG_OLDSUGGESTSVERSION = 1157, + RPMTAG_OLDSUGGESTSFLAGS = 1158, + RPMTAG_OLDENHANCESNAME = 1159, + RPMTAG_OLDENHANCESVERSION = 1160, + RPMTAG_OLDENHANCESFLAGS = 1161, + RPMTAG_PRIORITY = 1162, + RPMTAG_CVSID = 1163, + RPMTAG_BLINKPKGID = 1164, + RPMTAG_BLINKHDRID = 1165, + RPMTAG_BLINKNEVRA = 1166, + RPMTAG_FLINKPKGID = 1167, + RPMTAG_FLINKHDRID = 1168, + RPMTAG_FLINKNEVRA = 1169, + RPMTAG_PACKAGEORIGIN = 1170, + RPMTAG_TRIGGERPREIN = 1171, + RPMTAG_BUILDSUGGESTS = 1172, + RPMTAG_BUILDENHANCES = 1173, + RPMTAG_SCRIPTSTATES = 1174, + RPMTAG_SCRIPTMETRICS = 1175, + RPMTAG_BUILDCPUCLOCK = 1176, + RPMTAG_FILEDIGESTALGOS = 1177, + RPMTAG_VARIANTS = 1178, + RPMTAG_XMAJOR = 1179, + RPMTAG_XMINOR = 1180, + RPMTAG_REPOTAG = 1181, + RPMTAG_KEYWORDS = 1182, + RPMTAG_BUILDPLATFORMS = 1183, + RPMTAG_PACKAGECOLOR = 1184, + RPMTAG_PACKAGEPREFCOLOR = 1185, + RPMTAG_XATTRSDICT = 1186, + RPMTAG_FILEXATTRSX = 1187, + RPMTAG_DEPATTRSDICT = 1188, + RPMTAG_CONFLICTATTRSX = 1189, + RPMTAG_OBSOLETEATTRSX = 1190, + RPMTAG_PROVIDEATTRSX = 1191, + RPMTAG_REQUIREATTRSX = 1192, + RPMTAG_BUILDPROVIDES = 1193, + RPMTAG_BUILDOBSOLETES = 1194, + RPMTAG_DBINSTANCE = 1195, + RPMTAG_NVRA = 1196, + + /* tags 1997-4999 reserved */ + RPMTAG_FILENAMES = 5000, + RPMTAG_FILEPROVIDE = 5001, + RPMTAG_FILEREQUIRE = 5002, + RPMTAG_FSNAMES = 5003, + RPMTAG_FSSIZES = 5004, + RPMTAG_TRIGGERCONDS = 5005, + RPMTAG_TRIGGERTYPE = 5006, + RPMTAG_ORIGFILENAMES = 5007, + RPMTAG_LONGFILESIZES = 5008, + RPMTAG_LONGSIZE = 5009, + RPMTAG_FILECAPS = 5010, + RPMTAG_FILEDIGESTALGO = 5011, + RPMTAG_BUGURL = 5012, + RPMTAG_EVR = 5013, + RPMTAG_NVR = 5014, + RPMTAG_NEVR = 5015, + RPMTAG_NEVRA = 5016, + RPMTAG_HEADERCOLOR = 5017, + RPMTAG_VERBOSE = 5018, + RPMTAG_EPOCHNUM = 5019, + RPMTAG_PREINFLAGS = 5020, + RPMTAG_POSTINFLAGS = 5021, + RPMTAG_PREUNFLAGS = 5022, + RPMTAG_POSTUNFLAGS = 5023, + RPMTAG_PRETRANSFLAGS = 5024, + RPMTAG_POSTTRANSFLAGS = 5025, + RPMTAG_VERIFYSCRIPTFLAGS = 5026, + RPMTAG_TRIGGERSCRIPTFLAGS = 5027, + RPMTAG_COLLECTIONS = 5029, + RPMTAG_POLICYNAMES = 5030, + RPMTAG_POLICYTYPES = 5031, + RPMTAG_POLICYTYPESINDEXES = 5032, + RPMTAG_POLICYFLAGS = 5033, + RPMTAG_VCS = 5034, + RPMTAG_ORDERNAME = 5035, + RPMTAG_ORDERVERSION = 5036, + RPMTAG_ORDERFLAGS = 5037, + RPMTAG_MSSFMANIFEST = 5038, + RPMTAG_MSSFDOMAIN = 5039, + RPMTAG_INSTFILENAMES = 5040, + RPMTAG_REQUIRENEVRS = 5041, + RPMTAG_PROVIDENEVRS = 5042, + RPMTAG_OBSOLETENEVRS = 5043, + RPMTAG_CONFLICTNEVRS = 5044, + RPMTAG_FILENLINKS = 5045, + RPMTAG_RECOMMENDNAME = 5046, + RPMTAG_RECOMMENDVERSION = 5047, + RPMTAG_RECOMMENDFLAGS = 5048, + RPMTAG_SUGGESTNAME = 5049, + RPMTAG_SUGGESTVERSION = 5050, + RPMTAG_SUGGESTFLAGS = 5051, + RPMTAG_SUPPLEMENTNAME = 5052, + RPMTAG_SUPPLEMENTVERSION = 5053, + RPMTAG_SUPPLEMENTFLAGS = 5054, + RPMTAG_ENHANCENAME = 5055, + RPMTAG_ENHANCEVERSION = 5056, + RPMTAG_ENHANCEFLAGS = 5057, + RPMTAG_RECOMMENDNEVRS = 5058, + RPMTAG_SUGGESTNEVRS = 5059, + RPMTAG_SUPPLEMENTNEVRS = 5060, + RPMTAG_ENHANCENEVRS = 5061, + RPMTAG_ENCODING = 5062, + RPMTAG_FILETRIGGERIN = 5063, + RPMTAG_FILETRIGGERUN = 5064, + RPMTAG_FILETRIGGERPOSTUN = 5065, + RPMTAG_FILETRIGGERSCRIPTS = 5066, + RPMTAG_FILETRIGGERSCRIPTPROG = 5067, + RPMTAG_FILETRIGGERSCRIPTFLAGS = 5068, + RPMTAG_FILETRIGGERNAME = 5069, + RPMTAG_FILETRIGGERINDEX = 5070, + RPMTAG_FILETRIGGERVERSION = 5071, + RPMTAG_FILETRIGGERFLAGS = 5072, + RPMTAG_TRANSFILETRIGGERIN = 5073, + RPMTAG_TRANSFILETRIGGERUN = 5074, + RPMTAG_TRANSFILETRIGGERPOSTUN = 5075, + RPMTAG_TRANSFILETRIGGERSCRIPTS = 5076, + RPMTAG_TRANSFILETRIGGERSCRIPTPROG = 5077, + RPMTAG_TRANSFILETRIGGERSCRIPTFLAGS = 5078, + RPMTAG_TRANSFILETRIGGERNAME = 5079, + RPMTAG_TRANSFILETRIGGERINDEX = 5080, + RPMTAG_TRANSFILETRIGGERVERSION = 5081, + RPMTAG_TRANSFILETRIGGERFLAGS = 5082, + RPMTAG_REMOVEPATHPOSTFIXES = 5083, + RPMTAG_FILETRIGGERPRIORITIES = 5084, + RPMTAG_TRANSFILETRIGGERPRIORITIES = 5085, + RPMTAG_FILETRIGGERCONDS = 5086, + RPMTAG_FILETRIGGERTYPE = 5087, + RPMTAG_TRANSFILETRIGGERCONDS = 5088, + RPMTAG_TRANSFILETRIGGERTYPE = 5089, + RPMTAG_FILESIGNATURES = 5090, + RPMTAG_FILESIGNATURELENGTH = 5091, + RPMTAG_PAYLOADDIGEST = 5092, + RPMTAG_PAYLOADDIGESTALGO = 5093, + + /// + /// The file is a configuration file, and an existing file should be saved during a package upgrade operation and not removed during a pakage removal operation. + /// + RPMFILE_CONFIG = 1 << 0, + + /// + /// The file contains documentation. + /// + RPMFILE_DOC = 1 << 1, + + /// + /// This value is reserved for future use; conforming packages may not use this flag. + /// + RPMFILE_DONOTUSE = 1 << 2, + + /// + /// The file need not exist on the installed system. + /// + RPMFILE_MISSINGOK = 1 << 3, + + /// + /// Similar to the RPMFILE_CONFIG, this flag indicates that during an upgrade operation the original file on the system should not be altered. + /// + RPMFILE_NOREPLACE = 1 << 4, + + /// + /// The file is a package specification. + /// + RPMFILE_SPECFILE = 1 << 5, + + /// + /// The file is not actually included in the payload, but should still be considered as a part of the package. For example, a log file generated by the application at run time. + /// + RPMFILE_GHOST = 1 << 6, + + /// + /// The file contains the license conditions. + /// + RPMFILE_LICENSE = 1 << 7, + + /// + /// The file contains high level notes about the package. + /// + RPMFILE_README = 1 << 8, + + /// + /// The corresponding file is not a part of the package, and should not be installed. + /// + RPMFILE_EXCLUDE = 1 << 9, + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/IndexType.cs b/DebUOS/Packaging.Targets/Rpm/IndexType.cs new file mode 100644 index 0000000..0280214 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/IndexType.cs @@ -0,0 +1,16 @@ +namespace Packaging.Targets.Rpm +{ + internal enum IndexType : uint + { + RPM_NULL_TYPE = 0, + RPM_CHAR_TYPE = 1, + RPM_INT8_TYPE = 2, + RPM_INT16_TYPE = 3, + RPM_INT32_TYPE = 4, + RPM_INT64_TYPE = 5, + RPM_STRING_TYPE = 6, + RPM_BIN_TYPE = 7, + RPM_STRING_ARRAY_TYPE = 8, + RPM_I18NSTRING_TYPE = 9 + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/PackageDependency.cs b/DebUOS/Packaging.Targets/Rpm/PackageDependency.cs new file mode 100644 index 0000000..a05d90d --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/PackageDependency.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Packaging.Targets.Rpm +{ + /// + /// Represents a dependency on another package, or, a dependency on a specific RPM feature. + /// + internal class PackageDependency + { + /// + /// Initializes a new instance of the class. + /// + public PackageDependency() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The name of the dependency. + /// + /// + /// The dependency flags. + /// + /// + /// The dependency version. + /// + public PackageDependency(string name, RpmSense flags, string version) + { + this.Name = name; + this.Flags = flags; + this.Version = version; + } + + /// + /// Gets or sets a value indicating the dependency constraints. + /// + public RpmSense Flags + { + get; + set; + } + + /// + /// Gets or sets the name of the package which is a dependency. + /// + public string Name + { + get; + set; + } + + /// + /// Gets or sets the version of the package on which a dependency is taken. + /// + public string Version + { + get; + set; + } + + /// + public override bool Equals(object obj) + { + var other = obj as PackageDependency; + + if (other == null) + { + return false; + } + + return string.Equals(this.Name, other.Name, StringComparison.Ordinal) + && this.Flags == other.Flags + && string.Equals(this.Version, other.Version, StringComparison.Ordinal); + } + + /// + public override int GetHashCode() + { + return 13 * this.Name.GetHashCode() + 7 * (int)this.Flags + 7 * this.Version.GetHashCode(); + } + + /// + public override string ToString() + { + return $"{this.Name} {this.Flags} {this.Version}"; + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/PackageSigner.cs b/DebUOS/Packaging.Targets/Rpm/PackageSigner.cs new file mode 100644 index 0000000..21d7dde --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/PackageSigner.cs @@ -0,0 +1,30 @@ +using Org.BouncyCastle.Bcpg.OpenPgp; +using System; +using System.IO; + +namespace Packaging.Targets.Rpm +{ + /// + /// Generates values using a and a + /// . + /// + internal class PackageSigner : IPackageSigner + { + private readonly PgpPrivateKey privateKey; + + public PackageSigner(PgpPrivateKey privateKey) + { + if (privateKey == null) + { + throw new ArgumentNullException(nameof(privateKey)); + } + + this.privateKey = privateKey; + } + + public PgpSignature Sign(Stream payload) + { + return PgpSigner.Sign(this.privateKey, payload); + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/PgpHashAlgo.cs b/DebUOS/Packaging.Targets/Rpm/PgpHashAlgo.cs new file mode 100644 index 0000000..27d8ee6 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/PgpHashAlgo.cs @@ -0,0 +1,37 @@ +namespace Packaging.Targets.Rpm +{ + /// + /// Represents the various hashing algorithms. + /// + internal enum PgpHashAlgo + { + /// + /// The MD5 hashing algorithm. + /// + PGPHASHALGO_MD5 = 1, + + /// + /// The SHA1 hashing algorithm. + /// + PGPHASHALGO_SHA1 = 2, + PGPHASHALGO_RIPEMD160 = 3, + PGPHASHALGO_MD2 = 5, + PGPHASHALGO_TIGER192 = 6, + PGPHASHALGO_HAVAL_5_160 = 7, + + /// + /// The SHA 256 hashing algorithm. + /// + PGPHASHALGO_SHA256 = 8, + + /// + /// The SHA 384 hashing algorithm. + /// + PGPHASHALGO_SHA384 = 9, + + /// + /// The SHA 512 hashing algorithm. + /// + PGPHASHALGO_SHA512 = 10, + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/PgpSigner.cs b/DebUOS/Packaging.Targets/Rpm/PgpSigner.cs new file mode 100644 index 0000000..a29fa4d --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/PgpSigner.cs @@ -0,0 +1,177 @@ +using Org.BouncyCastle.Bcpg; +using Org.BouncyCastle.Bcpg.OpenPgp; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Packaging.Targets.Rpm +{ + /// + /// Provides helper methods for working with PGP signatures. + /// + internal class PgpSigner + { + /// + /// Verifies a PGP signature. + /// + /// + /// The PGP signature to verify. + /// + /// + /// A which contains the PGP public key. + /// + /// + /// The payload for which the signature was generated. + /// + /// + /// if the signature is valid; otherwise, . + /// + public static bool VerifySignature(PgpSignature signature, Stream publicKey, Stream payload) + { + PgpPublicKeyRingBundle keyRing = new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(publicKey)); + PgpPublicKey key = keyRing.GetPublicKey(signature.KeyId); + + return VerifySignature(signature, key, payload); + } + + /// + /// Verifies a PGP signature. + /// + /// + /// The PGP signature to verify. + /// + /// + /// The public key of the signer. + /// + /// + /// The payload for which the signature was generated. + /// + /// + /// if the signature is valid; otherwise, . + /// + public static bool VerifySignature(PgpSignature signature, PgpPublicKey key, Stream payload) + { + signature.InitVerify(key); + + byte[] buffer = new byte[1024]; + + int read; + while ((read = payload.Read(buffer, 0, buffer.Length)) > 0) + { + signature.Update(buffer, 0, read); + } + + return signature.Verify(); + } + + /// + /// Generates a as a digital signature for a certain payload. + /// + /// + /// The key with which to sign the data. + /// + /// + /// The payload to sign. + /// + /// + /// A which represents the signature. + /// + public static PgpSignature Sign(PgpPrivateKey key, Stream payload) + { + PgpV3SignatureGenerator signer = new PgpV3SignatureGenerator(PublicKeyAlgorithmTag.RsaGeneral, HashAlgorithmTag.Sha256); + signer.InitSign(0, key); + + byte[] buffer = new byte[1024]; + + int read; + while ((read = payload.Read(buffer, 0, buffer.Length)) > 0) + { + signer.Update(buffer, 0, read); + } + + return signer.Generate(); + } + + /// + /// Generates a + /// + /// + /// The name of the identity. + /// + /// + /// The passphrase used to protect the keyring. + /// + /// A . + /// + public static PgpKeyRingGenerator GenerateKeyRingGenerator(string identity, string password) + { + var rsaParams = new RsaKeyGenerationParameters(BigInteger.ValueOf(0x10001), new SecureRandom(), 2048, 12); + var symmetricAlgorithms = new SymmetricKeyAlgorithmTag[] + { + SymmetricKeyAlgorithmTag.Aes256, + SymmetricKeyAlgorithmTag.Aes192, + SymmetricKeyAlgorithmTag.Aes128 + }.Select(a => (int)a).ToArray(); + + var hashAlgorithms = new HashAlgorithmTag[] + { + HashAlgorithmTag.Sha256, + HashAlgorithmTag.Sha1, + HashAlgorithmTag.Sha384, + HashAlgorithmTag.Sha512, + HashAlgorithmTag.Sha224, + }.Select(a => (int)a).ToArray(); + + IAsymmetricCipherKeyPairGenerator generator = GeneratorUtilities.GetKeyPairGenerator("RSA"); + generator.Init(rsaParams); + + // Create the master (signing-only) key. + PgpKeyPair masterKeyPair = new PgpKeyPair( + PublicKeyAlgorithmTag.RsaSign, + generator.GenerateKeyPair(), + DateTime.UtcNow); + + PgpSignatureSubpacketGenerator masterSubpckGen + = new PgpSignatureSubpacketGenerator(); + masterSubpckGen.SetKeyFlags(false, PgpKeyFlags.CanSign + | PgpKeyFlags.CanCertify); + masterSubpckGen.SetPreferredSymmetricAlgorithms(false, symmetricAlgorithms); + masterSubpckGen.SetPreferredHashAlgorithms(false, hashAlgorithms); + + // Create a signing and encryption key for daily use. + PgpKeyPair encKeyPair = new PgpKeyPair( + PublicKeyAlgorithmTag.RsaGeneral, + generator.GenerateKeyPair(), + DateTime.UtcNow); + + PgpSignatureSubpacketGenerator encSubpckGen = new PgpSignatureSubpacketGenerator(); + encSubpckGen.SetKeyFlags(false, PgpKeyFlags.CanEncryptCommunications | PgpKeyFlags.CanEncryptStorage); + + masterSubpckGen.SetPreferredSymmetricAlgorithms(false, symmetricAlgorithms); + masterSubpckGen.SetPreferredHashAlgorithms(false, hashAlgorithms); + + // Create the key ring. + PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator( + PgpSignature.DefaultCertification, + masterKeyPair, + identity, + SymmetricKeyAlgorithmTag.Aes128, + password.ToCharArray(), + true, + masterSubpckGen.Generate(), + null, + new SecureRandom()); + + // Add encryption subkey. + keyRingGen.AddSubKey(encKeyPair, encSubpckGen.Generate(), null); + + return keyRingGen; + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmDumper.cs b/DebUOS/Packaging.Targets/Rpm/RpmDumper.cs new file mode 100644 index 0000000..7189a0f --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmDumper.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; + +namespace Packaging.Targets.Rpm +{ + /// + /// Dumps the contents of the headers of a RPM package to a text file. Useful when comparing two + /// RPM packages. + /// + internal static class RpmDumper + { + public static void Dump(RpmPackage package, string path) + { + using (var file = File.Open(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) + using (StreamWriter writer = new StreamWriter(file)) + { + Dump(package, writer); + } + } + + public static void Dump(RpmPackage package, StreamWriter writer) + { + writer.WriteLine("Lead:"); + writer.WriteLine(" ArchNum {0}", package.Lead.ArchNum); + writer.WriteLine(" Magic {0}", package.Lead.Magic); + writer.WriteLine(" Major {0}", package.Lead.Major); + writer.WriteLine(" Minor {0}", package.Lead.Minor); + writer.WriteLine(" Name {0}", package.Lead.Name); + writer.WriteLine(" OsNum {0}", package.Lead.OsNum); + writer.WriteLine(" SignatureType {0}", package.Lead.SignatureType); + writer.WriteLine(" Type {0}", package.Lead.Type); + + writer.WriteLine(); + + writer.WriteLine("Signature:"); + Dump(package.Signature, writer); + + writer.WriteLine("Header:"); + Dump(package.Header, writer); + } + + public static void Dump(Section section, StreamWriter writer) + { + foreach (var record in section.Records) + { + writer.WriteLine(" {0}:", record.Key); + writer.WriteLine(" Count: {0}", record.Value.Header.Count); + writer.WriteLine(" Offset: {0}", record.Value.Header.Offset); + writer.WriteLine(" Tag: {0}", record.Value.Header.Tag); + writer.WriteLine(" Type: {0}", record.Value.Header.Type); + + if (record.Value.Value is IEnumerable) + { + writer.WriteLine(" Value: {0}", BitConverter.ToString((byte[])record.Value.Value).Replace("-", string.Empty)); + } + else if (record.Value.Value is IEnumerable) + { + writer.Write(" Value: "); + bool isFirst = true; + + foreach (var value in (IEnumerable)record.Value.Value) + { + if (isFirst) + { + isFirst = false; + } + else + { + writer.Write(", "); + } + + writer.Write(value); + } + + writer.WriteLine(); + } + else + { + writer.WriteLine(" Value: {0}", record.Value.Value); + } + } + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmFile.cs b/DebUOS/Packaging.Targets/Rpm/RpmFile.cs new file mode 100644 index 0000000..0a7704b --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmFile.cs @@ -0,0 +1,182 @@ +using Packaging.Targets.IO; +using System; +using System.Collections.ObjectModel; + +namespace Packaging.Targets.Rpm +{ + /// + /// Represents a file in an RPM package. + /// + internal class RpmFile + { + /// + /// Gets or sets the size of the file. + /// + public int Size + { + get; + set; + } + + /// + /// Gets or sets the file mode. + /// + public LinuxFileMode Mode + { + get; + set; + } + + /// + /// Gets or sets the device ID of the file, if the file is a special file. + /// + public short Rdev + { + get; + set; + } + + /// + /// Gets or sets the time at which the file was last modified. + /// + public DateTimeOffset ModifiedTime + { + get; + set; + } + + /// + /// Gets or sets the MD5 hash of the file. + /// + public byte[] MD5Hash + { + get; + set; + } + + /// + /// Gets or sets the name of the file to which this file links. + /// + public string LinkTo + { + get; + set; + } + + /// + /// Gets or sets information which describes this file. + /// + public RpmFileFlags Flags + { + get; + set; + } + + /// + /// Gets or sets the user name of the owner of the file. + /// + public string UserName + { + get; + set; + } + + /// + /// Gets or sets the group name of the owner of the file. + /// + public string GroupName + { + get; + set; + } + + /// + /// Gets or sets a value indicating how the file should be validated. + /// + public RpmVerifyFlags VerifyFlags + { + get; + set; + } + + /// + /// Gets or sets the ID of device containing file. + /// + public int Device + { + get; + set; + } + + /// + /// Gets or sets the inode number of the file. + /// + public int Inode + { + get; + set; + } + + /// + /// Gets or sets a per-file locale marker used to install only locale specific subsets of files when the package is installed. + /// + public string Lang + { + get; + set; + } + + /// + /// Gets or set sthe file color, a classification of file types. + /// + public RpmFileColor Color + { + get; + set; + } + + /// + /// Gets or sets the class of the file. This is usually the output of the file command for this file. + /// + public string Class + { + get; + set; + } + + /// + /// Gets or sets a list of all dependencies of this file. If the file is an ELF file, the dependencies + /// are the ELF dependencies. + /// + public Collection Requires + { + get; + set; + } + + /// + /// Gets or sets a list of all dependencies fulfilled by this file. If the file is an ELF file, + /// these are the ELF provides. + /// + public Collection Provides + { + get; + set; + } + + /// + /// Gets or sets the name of the file. + /// + public string Name + { + get; + set; + } + + /// + public override string ToString() + { + return this.Name; + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmFileColor.cs b/DebUOS/Packaging.Targets/Rpm/RpmFileColor.cs new file mode 100644 index 0000000..f81dbdf --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmFileColor.cs @@ -0,0 +1,44 @@ +using System; + +namespace Packaging.Targets.Rpm +{ + /// + /// Determines the type of the file. + /// + [Flags] + internal enum RpmFileColor + { + RPMFC_BLACK = 0, + RPMFC_ELF32 = 1 << 0, + RPMFC_ELF64 = 1 << 1, + RPMFC_ELFMIPSN32 = 1 << 2, + RPMFC_PKGCONFIG = 1 << 4, + RPMFC_LIBTOOL = 1 << 5, + RPMFC_BOURNE = 1 << 6, + RPMFC_MODULE = 1 << 7, + RPMFC_EXECUTABLE = 1 << 8, + RPMFC_SCRIPT = 1 << 9, + RPMFC_TEXT = 1 << 10, + RPMFC_DATA = 1 << 11, + RPMFC_DOCUMENT = 1 << 12, + RPMFC_STATIC = 1 << 13, + RPMFC_NOTSTRIPPED = 1 << 14, + RPMFC_COMPRESSED = 1 << 15, + RPMFC_DIRECTORY = 1 << 16, + RPMFC_SYMLINK = 1 << 17, + RPMFC_DEVICE = 1 << 18, + RPMFC_LIBRARY = 1 << 19, + RPMFC_ARCHIVE = 1 << 20, + RPMFC_FONT = 1 << 21, + RPMFC_IMAGE = 1 << 22, + RPMFC_MANPAGE = 1 << 23, + RPMFC_PERL = 1 << 24, + RPMFC_JAVA = 1 << 25, + RPMFC_PYTHON = 1 << 26, + RPMFC_PHP = 1 << 27, + RPMFC_TCL = 1 << 28, + RPMFC_WHITE = 1 << 29, + RPMFC_INCLUDE = 1 << 30, + RPMFC_ERROR = 1 << 31 + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmFileFlags.cs b/DebUOS/Packaging.Targets/Rpm/RpmFileFlags.cs new file mode 100644 index 0000000..71f8a3b --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmFileFlags.cs @@ -0,0 +1,20 @@ +namespace Packaging.Targets.Rpm +{ + /// + /// Determines the type of a file. + /// + internal enum RpmFileFlags + { + None = 0, + RPMFILE_CONFIG = 1 << 0, + RPMFILE_DOC = 1 << 1, + RPMFILE_DONOTUSE = 1 << 2, + RPMFILE_MISSINGOK = 1 << 3, + RPMFILE_NOREPLACE = 1 << 4, + RPMFILE_SPECFILE = 1 << 5, + RPMFILE_GHOST = 1 << 6, + RPMFILE_LICENSE = 1 << 7, + RPMFILE_README = 1 << 8, + RPMFILE_EXCLUDE = 1 << 9 + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmHeader.cs b/DebUOS/Packaging.Targets/Rpm/RpmHeader.cs new file mode 100644 index 0000000..3ef3cd2 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmHeader.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Packaging.Targets.Rpm +{ + internal struct RpmHeader + { + /// + /// Value identifying this record as an RPM header record. This value shall be "\216\255\350\001". + /// + public uint Magic; + + /// + /// Reserved space. This value shall be "\000\000\000\000". + /// + public uint Reserved; + + /// + /// The number of Index Records that follow this Header Record. There should be at least 1 Index Record. + /// + public uint IndexCount; + + /// + /// The size in bytes of the storage area for the data pointed to by the Index Records. + /// + public uint HeaderSize; + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmLead.cs b/DebUOS/Packaging.Targets/Rpm/RpmLead.cs new file mode 100644 index 0000000..4b204aa --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmLead.cs @@ -0,0 +1,70 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Packaging.Targets.Rpm +{ + /// + /// The lead section is used to identify the package file. + /// + /// + internal struct RpmLead + { + /// + /// Value identifying this file as an RPM format file. This value shall be "\355\253\356\333". + /// + public uint Magic; + + /// + /// Value indicating the major version number of the file format version. This value shall be 3. + /// + public byte Major; + + /// + /// Value indicating the minor revision number of file format version. This value shall be 0. + /// + public byte Minor; + + /// + /// Value indicating whether this is a source or binary package. This value shall be 0 to indicate a binary package. + /// + public ushort Type; + + /// + /// Value indicating the architecture for which this package is valid. This value is specified in the relevant architecture + /// specific part of ISO/IEC 23360. + /// + public ushort ArchNum; + + /// + /// A NUL terminated string that provides the package name. This name shall conform with the Package Naming section of this specification. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 66)] + public byte[] NameBytes; + + /// + /// Value indicating the Operating System for which this package is valid. This value shall be 1. + /// + public ushort OsNum; + + /// + /// Value indicating the type of the signature used in the Signature part of the file. This value shall be 5. + /// + public ushort SignatureType; + + /// + /// Reserved space. The value is undefined. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] Reserved; + + public string Name + { + get + { + var length = Array.FindIndex(this.NameBytes, 0, (x) => x == 0); + return Encoding.ASCII.GetString(this.NameBytes, 0, length); + } + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmMetadata.cs b/DebUOS/Packaging.Targets/Rpm/RpmMetadata.cs new file mode 100644 index 0000000..52ebf98 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmMetadata.cs @@ -0,0 +1,1019 @@ +using Packaging.Targets.IO; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; + +namespace Packaging.Targets.Rpm +{ + /// + /// Provides access to the metadata stored in the RPM package. + /// + internal class RpmMetadata + { + public RpmMetadata(RpmPackage package) + { + if (package == null) + { + throw new ArgumentNullException(nameof(package)); + } + + this.Package = package; + } + + public RpmPackage Package + { + get; + private set; + } + + /// + /// Gets or sets the length of the immutable region, starting from the position of the + /// record. This record should be the last record in the signature block, and the value is negative, indicating the previous + /// blocks are considered immutable. + /// + public int ImmutableRegionSize + { + get + { + // For more information about immutable regions, see: + // http://ftp.rpm.org/api/4.4.2.2/hregions.html + // https://dentrassi.de/2016/04/15/writing-rpm-files-in-plain-java/ + // https://blog.bethselamin.de/posts/argh-pm.html + // For now, we're always assuming the entire header and the entire signature is immutable + var immutableSignatureRegion = this.GetByteArray(IndexTag.RPMTAG_HEADERIMMUTABLE); + + using (MemoryStream s = new MemoryStream(immutableSignatureRegion)) + { + var h = s.ReadStruct(); + return h.Offset; + } + } + + set + { + IndexHeader header = default(IndexHeader); + header.Offset = value; + header.Count = 0x10; + header.Type = IndexType.RPM_BIN_TYPE; + header.Tag = (uint)IndexTag.RPMTAG_HEADERIMMUTABLE; + + byte[] data; + + using (MemoryStream s = new MemoryStream()) + { + s.WriteStruct(header); + data = s.ToArray(); + } + + this.SetByteArray(IndexTag.RPMTAG_HEADERIMMUTABLE, data); + } + } + + /// + /// Gets or sets a list of all locales supported by this package. The default, invariant locale is marked as the C locale. + /// + public Collection Locales + { + get { return this.GetStringArray(IndexTag.RPMTAG_HEADERI18NTABLE); } + set { this.SetStringArray(IndexTag.RPMTAG_HEADERI18NTABLE, value.ToArray()); } + } + + /// + /// Gets or sets the name of the package. + /// + public string Name + { + get { return this.GetString(IndexTag.RPMTAG_NAME); } + set { this.SetString(IndexTag.RPMTAG_NAME, value); } + } + + /// + /// Gets or sets the package version. + /// + public string Version + { + get { return this.GetString(IndexTag.RPMTAG_VERSION); } + set { this.SetString(IndexTag.RPMTAG_VERSION, value); } + } + + /// + /// Gets or sets the release number of the package. + /// + public string Release + { + get { return this.GetString(IndexTag.RPMTAG_RELEASE); } + set { this.SetString(IndexTag.RPMTAG_RELEASE, value); } + } + + /// + /// Gets or sets the a summary (one line) description of the package. + /// + public string Summary + { + get { return this.GetLocalizedString(IndexTag.RPMTAG_SUMMARY); } + set { this.SetLocalizedString(IndexTag.RPMTAG_SUMMARY, value); } + } + + /// + /// Gets or sets the full description of the package. + /// + public string Description + { + get { return this.GetLocalizedString(IndexTag.RPMTAG_DESCRIPTION); } + set { this.SetLocalizedString(IndexTag.RPMTAG_DESCRIPTION, value); } + } + + /// + /// Gets or sets the date and time at which the package was built. + /// + public DateTimeOffset BuildTime + { + get { return DateTimeOffset.FromUnixTimeSeconds(this.GetInt(IndexTag.RPMTAG_BUILDTIME)); } + set { this.SetInt(IndexTag.RPMTAG_BUILDTIME, (int)value.ToUnixTimeSeconds()); } + } + + /// + /// Gets or sets the name of the host on which the package was built. + /// + public string BuildHost + { + get { return this.GetString(IndexTag.RPMTAG_BUILDHOST); } + set { this.SetString(IndexTag.RPMTAG_BUILDHOST, value); } + } + + /// + /// Gets or sets the sum of the sizes of the regular files in the package. + /// + public int Size + { + get { return this.GetInt(IndexTag.RPMTAG_SIZE); } + set { this.SetInt(IndexTag.RPMTAG_SIZE, value); } + } + + /// + /// Gets or sets the name of the distribution for which the package was built. + /// + public string Distribution + { + get { return this.GetString(IndexTag.RPMTAG_DISTRIBUTION); } + set { this.SetString(IndexTag.RPMTAG_DISTRIBUTION, value); } + } + + /// + /// Gets or sets the name of the organization which produced the package. + /// + public string Vendor + { + get { return this.GetString(IndexTag.RPMTAG_VENDOR); } + set { this.SetString(IndexTag.RPMTAG_VENDOR, value); } + } + + /// + /// Gets or sets the license which applies to this package. + /// + public string License + { + get { return this.GetString(IndexTag.RPMTAG_LICENSE); } + set { this.SetString(IndexTag.RPMTAG_LICENSE, value); } + } + + /// + /// Gets or sets the administrative group to which this package belongs. + /// + public string Group + { + get { return this.GetLocalizedString(IndexTag.RPMTAG_GROUP); } + set { this.SetLocalizedString(IndexTag.RPMTAG_GROUP, value); } + } + + /// + /// Gets or sets the generic package information URL. + /// + public string Url + { + get { return this.GetString(IndexTag.RPMTAG_URL); } + set { this.SetString(IndexTag.RPMTAG_URL, value); } + } + + /// + /// Gets or sets the generic name of the OS for which this package was built. Should be linux. + /// + public string Os + { + get { return this.GetString(IndexTag.RPMTAG_OS); } + set { this.SetString(IndexTag.RPMTAG_OS, value); } + } + + /// + /// Gets or sets the name of the architecture for which the package was built, as defined in the architecture-specific + /// LSB specifications. + /// + public string Arch + { + get { return this.GetString(IndexTag.RPMTAG_ARCH); } + set { this.SetString(IndexTag.RPMTAG_ARCH, value); } + } + + /// + /// Gets or sets the name of the source RPM. + /// + public string SourceRpm + { + get { return this.GetString(IndexTag.RPMTAG_SOURCERPM); } + set { this.SetString(IndexTag.RPMTAG_SOURCERPM, value); } + } + + /// + /// Gets or sets the version of the RPM tool used to build this package. + /// + public string RpmVersion + { + get { return this.GetString(IndexTag.RPMTAG_RPMVERSION); } + set { this.SetString(IndexTag.RPMTAG_RPMVERSION, value); } + } + + /// + /// Gets or sets the scipt to run before installation of this package. + /// + public string PreIn + { + get { return this.GetString(IndexTag.RPMTAG_PREIN); } + set { this.SetString(IndexTag.RPMTAG_PREIN, value); } + } + + /// + /// Gets or sets the scipt to run after installation of this package. + /// + public string PostIn + { + get { return this.GetString(IndexTag.RPMTAG_POSTIN); } + set { this.SetString(IndexTag.RPMTAG_POSTIN, value); } + } + + /// + /// Gets or sets the scipt to run before removal of this package. + /// + public string PreUn + { + get { return this.GetString(IndexTag.RPMTAG_PREUN); } + set { this.SetString(IndexTag.RPMTAG_PREUN, value); } + } + + /// + /// Gets or sets the scipt to run after removal of this package. + /// + public string PostUn + { + get { return this.GetString(IndexTag.RPMTAG_POSTUN); } + set { this.SetString(IndexTag.RPMTAG_POSTUN, value); } + } + + /// + /// Gets or sets the name of the program to run before installation of this package. + /// + public string PreInProg + { + get { return this.GetString(IndexTag.RPMTAG_PREINPROG); } + set { this.SetString(IndexTag.RPMTAG_PREINPROG, value); } + } + + /// + /// Gets or sets the name of the program to run after installation of this package. + /// + public string PostInProg + { + get { return this.GetString(IndexTag.RPMTAG_POSTINPROG); } + set { this.SetString(IndexTag.RPMTAG_POSTINPROG, value); } + } + + /// + /// Gets or sets the name of the program to run before removal of this package. + /// + public string PreUnProg + { + get { return this.GetString(IndexTag.RPMTAG_PREUNPROG); } + set { this.SetString(IndexTag.RPMTAG_PREUNPROG, value); } + } + + /// + /// Gets or sets the name of the program to run after removal of this package. + /// + public string PostUnProg + { + get { return this.GetString(IndexTag.RPMTAG_POSTUNPROG); } + set { this.SetString(IndexTag.RPMTAG_POSTUNPROG, value); } + } + + /// + /// Gets or sets an opaque string whose contents are undefined. + /// + public string Cookie + { + get { return this.GetString(IndexTag.RPMTAG_COOKIE); } + set { this.SetString(IndexTag.RPMTAG_COOKIE, value); } + } + + /// + /// Gets or sets additional flags which may have been passed to the compiler when building this package. + /// + public string OptFlags + { + get { return this.GetString(IndexTag.RPMTAG_OPTFLAGS); } + set { this.SetString(IndexTag.RPMTAG_OPTFLAGS, value); } + } + + /// + /// Gets or sets the URL for the package. + /// + public string DistUrl + { + get { return this.GetString(IndexTag.RPMTAG_DISTURL); } + set { this.SetString(IndexTag.RPMTAG_DISTURL, value); } + } + + /// + /// Gets or sets the format of the payload. Should be cpio. + /// + public string PayloadFormat + { + get { return this.GetString(IndexTag.RPMTAG_PAYLOADFORMAT); } + set { this.SetString(IndexTag.RPMTAG_PAYLOADFORMAT, value); } + } + + /// + /// Gets or sets the name of the compressor used to compress the payload. + /// + public string PayloadCompressor + { + get { return this.GetString(IndexTag.RPMTAG_PAYLOADCOMPRESSOR); } + set { this.SetString(IndexTag.RPMTAG_PAYLOADCOMPRESSOR, value); } + } + + /// + /// Gets or sets the compression level used for the payload. + /// + public string PayloadFlags + { + get { return this.GetString(IndexTag.RPMTAG_PAYLOADFLAGS); } + set { this.SetString(IndexTag.RPMTAG_PAYLOADFLAGS, value); } + } + + /// + /// Gets or sets an opaque string whose value is undefined. + /// + public string Platform + { + get { return this.GetString(IndexTag.RPMTAG_PLATFORM); } + set { this.SetString(IndexTag.RPMTAG_PLATFORM, value); } + } + + public byte[] SourcePkgId + { + get { return this.GetByteArray(IndexTag.RPMTAG_SOURCEPKGID); } + set { this.SetByteArray(IndexTag.RPMTAG_SOURCEPKGID, value); } + } + + /// + /// Gets or sets the hashing algorithm used to calculate the hash of files embedded + /// in this RPM archive. + /// + public PgpHashAlgo FileDigetsAlgo + { + get { return (PgpHashAlgo)this.GetInt(IndexTag.RPMTAG_FILEDIGESTALGO); } + set { this.SetInt(IndexTag.RPMTAG_FILEDIGESTALGO, (int)value); } + } + + /// + /// Gets or sets all change log entries. + /// + public IEnumerable ChangelogEntries + { + get + { + var times = this.GetIntArray(IndexTag.RPMTAG_CHANGELOGTIME); + var names = this.GetStringArray(IndexTag.RPMTAG_CHANGELOGNAME); + var text = this.GetStringArray(IndexTag.RPMTAG_CHANGELOGTEXT); + + int count = Math.Min(times.Count, Math.Min(names.Count, text.Count)); + + for (int i = 0; i < count; i++) + { + yield return new ChangelogEntry() + { + Date = DateTimeOffset.FromUnixTimeSeconds(times[i]), + Name = names[i], + Text = text[i] + }; + } + } + + set + { + var entries = value.ToArray(); + + var times = new int[entries.Length]; + var names = new string[entries.Length]; + var text = new string[entries.Length]; + + for (int i = 0; i < entries.Length; i++) + { + times[i] = (int)entries[i].Date.ToUnixTimeSeconds(); + names[i] = entries[i].Name; + text[i] = entries[i].Text; + } + + this.SetIntArray(IndexTag.RPMTAG_CHANGELOGTIME, times); + this.SetStringArray(IndexTag.RPMTAG_CHANGELOGNAME, names); + this.SetStringArray(IndexTag.RPMTAG_CHANGELOGTEXT, text); + } + } + + /// + /// Gets or sets a list of all files embedded in this package. + /// + public IEnumerable Files + { + get + { + var sizes = this.GetIntArray(IndexTag.RPMTAG_FILESIZES); + var modes = this.GetShortArray(IndexTag.RPMTAG_FILEMODES); + var rdevs = this.GetShortArray(IndexTag.RPMTAG_FILERDEVS); + var mtimes = this.GetIntArray(IndexTag.RPMTAG_FILEMTIMES); + var md5s = this.GetStringArray(IndexTag.RPMTAG_FILEDIGESTS); + var linkTos = this.GetStringArray(IndexTag.RPMTAG_FILELINKTOS); + var flags = this.GetIntArray(IndexTag.RPMTAG_FILEFLAGS); + var usernames = this.GetStringArray(IndexTag.RPMTAG_FILEUSERNAME); + var groupnames = this.GetStringArray(IndexTag.RPMTAG_FILEGROUPNAME); + var verifyFlags = this.GetIntArray(IndexTag.RPMTAG_FILEVERIFYFLAGS); + var devices = this.GetIntArray(IndexTag.RPMTAG_FILEDEVICES); + var inodes = this.GetIntArray(IndexTag.RPMTAG_FILEINODES); + var langs = this.GetStringArray(IndexTag.RPMTAG_FILELANGS); + var colors = this.GetIntArray(IndexTag.RPMTAG_FILECOLORS); + var classes = this.GetIntArray(IndexTag.RPMTAG_FILECLASS); + var dependsX = this.GetIntArray(IndexTag.RPMTAG_FILEDEPENDSX); + var dependsN = this.GetIntArray(IndexTag.RPMTAG_FILEDEPENDSN); + + var baseNames = this.GetStringArray(IndexTag.RPMTAG_BASENAMES); + var dirIndexes = this.GetIntArray(IndexTag.RPMTAG_DIRINDEXES); + var dirNames = this.GetStringArray(IndexTag.RPMTAG_DIRNAMES); + + var classDict = this.GetStringArray(IndexTag.RPMTAG_CLASSDICT); + var dependsDict = this.GetIntArray(IndexTag.RPMTAG_DEPENDSDICT); + + for (int i = 0; i < sizes.Count; i++) + { + Collection requires = new Collection(); + Collection provides = new Collection(); + + for (int j = dependsX[i]; j < dependsX[i] + dependsN[i]; j++) + { + // https://github.com/rpm-software-management/rpm/blob/8f509d669b9ae79c86dd510c5a4bc5109f60d733/build/rpmfc.c#L734 + var value = dependsDict[j]; + + var dependencyType = this.GetDependencyTag((char)((value >> 24) & 0xFF)); + var index = value & 0x00ffffff; + + var values = this.GetStringArray(dependencyType); + Collection versions; + RpmSense[] types; + + switch (dependencyType) + { + case IndexTag.RPMTAG_REQUIRENAME: + versions = this.GetStringArray(IndexTag.RPMTAG_REQUIREVERSION); + types = this.GetIntArray(IndexTag.RPMTAG_REQUIREFLAGS).Select(e => (RpmSense)e).ToArray(); + break; + + case IndexTag.RPMTAG_PROVIDENAME: + versions = this.GetStringArray(IndexTag.RPMTAG_PROVIDEVERSION); + types = this.GetIntArray(IndexTag.RPMTAG_PROVIDEFLAGS).Select(e => (RpmSense)e).ToArray(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(dependencyType)); + } + + var dependencyName = values[index]; + var dependencyVersion = versions[index]; + var dependencyFlags = types[index]; + + var dependency = new PackageDependency() + { + Flags = dependencyFlags, + Name = dependencyName, + Version = dependencyVersion + }; + + switch (dependencyType) + { + case IndexTag.RPMTAG_REQUIRENAME: + requires.Add(dependency); + break; + + case IndexTag.RPMTAG_PROVIDENAME: + provides.Add(dependency); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(dependencyType)); + } + } + + var directoryName = dirNames[dirIndexes[i]]; + var name = $"{directoryName}{baseNames[i]}"; + + yield return new RpmFile() + { + Size = sizes[i], + Mode = (LinuxFileMode)modes[i], + Rdev = rdevs[i], + ModifiedTime = DateTimeOffset.FromUnixTimeSeconds(mtimes[i]), + MD5Hash = RpmSignature.StringToByteArray(md5s[i]), + LinkTo = linkTos[i], + Flags = (RpmFileFlags)flags[i], + UserName = usernames[i], + GroupName = groupnames[i], + VerifyFlags = (RpmVerifyFlags)verifyFlags[i], + Device = devices[i], + Inode = inodes[i], + Lang = langs[i], + Color = (RpmFileColor)colors[i], + Class = classDict[classes[i]], + Requires = requires, + Provides = provides, + Name = name + }; + } + } + + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + var files = value.ToArray(); + + var sizes = new int[files.Length]; + var modes = new short[files.Length]; + var rdevs = new short[files.Length]; + var mtimes = new int[files.Length]; + var md5s = new string[files.Length]; + var linkTos = new string[files.Length]; + var flags = new int[files.Length]; + var usernames = new string[files.Length]; + var groupnames = new string[files.Length]; + var verifyFlags = new int[files.Length]; + var devices = new int[files.Length]; + var inodes = new int[files.Length]; + var langs = new string[files.Length]; + var colors = new int[files.Length]; + var classes = new int[files.Length]; + + var baseNames = new string[files.Length]; + var dirIndexes = new int[files.Length]; + var dirNames = new Collection(); + + var dependsX = new int[files.Length]; + var dependsN = new int[files.Length]; + var classDict = new Collection(); + var dependsDict = new Collection(); + + var currentDependencies = this.Dependencies.ToList(); + var currentProvides = this.Provides.ToList(); + + // Prepopulate the new dependencies in sorted order + var allDependencies = files + .SelectMany(f => f.Requires) + .Distinct() + .Where(d => !currentDependencies.Contains(d)) + .OrderBy(f => f.Name) + .ToList(); + + var allProvides = files + .SelectMany(f => f.Provides) + .Distinct() + .Where(d => !currentProvides.Contains(d)) + .ToList(); + + currentDependencies.AddRange(allDependencies); + this.Dependencies = currentDependencies; + + currentProvides.AddRange(allProvides); + this.Provides = currentProvides; + + var requireNames = this.GetStringArray(IndexTag.RPMTAG_REQUIRENAME); + var provideNames = this.GetStringArray(IndexTag.RPMTAG_PROVIDENAME); + + for (int i = 0; i < files.Length; i++) + { + var file = files[i]; + sizes[i] = file.Size; + modes[i] = (short)file.Mode; + rdevs[i] = file.Rdev; + mtimes[i] = (int)file.ModifiedTime.ToUnixTimeSeconds(); + 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; + verifyFlags[i] = (int)file.VerifyFlags; + devices[i] = file.Device; + inodes[i] = file.Inode; + langs[i] = file.Lang; + colors[i] = (int)file.Color; + flags[i] = (int)file.Flags; + + if (!classDict.Contains(file.Class)) + { + classDict.Add(file.Class); + } + + classes[i] = classDict.IndexOf(file.Class); + + var dirName = Path.GetDirectoryName(file.Name).Replace('\\', '/') + '/'; + var fileName = Path.GetFileName(file.Name); + + if (!dirNames.Contains(dirName)) + { + dirNames.Add(dirName); + } + + dirIndexes[i] = dirNames.IndexOf(dirName); + baseNames[i] = fileName; + + int dependX = dependsDict.Count; + dependsN[i] = file.Requires.Count + file.Provides.Count; + dependsX[i] = dependsN[i] == 0 ? 0 : dependX; + + foreach (var provide in file.Provides) + { + byte type = (byte)this.GetDependencyType(IndexTag.RPMTAG_PROVIDENAME); + + // Incomplete - we should check for not only name but also flags & version + var index = provideNames.IndexOf(provide.Name); + index |= type << 24; + + dependsDict.Add(index); + } + + foreach (var dependency in file.Requires) + { + byte type = (byte)this.GetDependencyType(IndexTag.RPMTAG_REQUIRENAME); + + // Incomplete - we should check for not only name but also flags & version + var index = requireNames.IndexOf(dependency.Name); + index |= type << 24; + + dependsDict.Add(index); + } + } + + this.SetIntArray(IndexTag.RPMTAG_FILESIZES, sizes); + this.SetShortArray(IndexTag.RPMTAG_FILEMODES, modes); + this.SetShortArray(IndexTag.RPMTAG_FILERDEVS, rdevs); + this.SetIntArray(IndexTag.RPMTAG_FILEMTIMES, mtimes); + this.SetStringArray(IndexTag.RPMTAG_FILEDIGESTS, md5s); + this.SetStringArray(IndexTag.RPMTAG_FILELINKTOS, linkTos); + this.SetIntArray(IndexTag.RPMTAG_FILEFLAGS, flags); + this.SetStringArray(IndexTag.RPMTAG_FILEUSERNAME, usernames); + this.SetStringArray(IndexTag.RPMTAG_FILEGROUPNAME, groupnames); + this.SetIntArray(IndexTag.RPMTAG_FILEVERIFYFLAGS, verifyFlags); + this.SetIntArray(IndexTag.RPMTAG_FILEDEVICES, devices); + this.SetIntArray(IndexTag.RPMTAG_FILEINODES, inodes); + this.SetStringArray(IndexTag.RPMTAG_FILELANGS, langs); + this.SetIntArray(IndexTag.RPMTAG_FILECOLORS, colors); + this.SetIntArray(IndexTag.RPMTAG_FILECLASS, classes); + this.SetIntArray(IndexTag.RPMTAG_FILEDEPENDSX, dependsX); + this.SetIntArray(IndexTag.RPMTAG_FILEDEPENDSN, dependsN); + + this.SetStringArray(IndexTag.RPMTAG_BASENAMES, baseNames); + this.SetIntArray(IndexTag.RPMTAG_DIRINDEXES, dirIndexes); + this.SetStringArray(IndexTag.RPMTAG_DIRNAMES, dirNames.ToArray()); + + this.SetStringArray(IndexTag.RPMTAG_CLASSDICT, classDict.ToArray()); + this.SetIntArray(IndexTag.RPMTAG_DEPENDSDICT, dependsDict.ToArray()); + + this.Size = files.Where(f => f.Mode.HasFlag(LinuxFileMode.S_IFREG)).Sum(f => f.Size) - 0x24; + } + } + + public IEnumerable Dependencies + { + get + { + var flags = this.GetIntArray(IndexTag.RPMTAG_REQUIREFLAGS); + var names = this.GetStringArray(IndexTag.RPMTAG_REQUIRENAME); + var vers = this.GetStringArray(IndexTag.RPMTAG_REQUIREVERSION); + + for (int i = 0; i < flags.Count; i++) + { + yield return new PackageDependency() + { + Flags = (RpmSense)flags[i], + Name = names[i], + Version = vers[i] + }; + } + } + + set + { + var dependencies = value.ToArray(); + + var flags = new int[dependencies.Length]; + var names = new string[dependencies.Length]; + var vers = new string[dependencies.Length]; + + for (int i = 0; i < dependencies.Length; i++) + { + flags[i] = (int)dependencies[i].Flags; + names[i] = dependencies[i].Name; + vers[i] = dependencies[i].Version; + } + + this.SetIntArray(IndexTag.RPMTAG_REQUIREFLAGS, flags); + this.SetStringArray(IndexTag.RPMTAG_REQUIRENAME, names); + this.SetStringArray(IndexTag.RPMTAG_REQUIREVERSION, vers); + } + } + + public IEnumerable Provides + { + get + { + var flags = this.GetIntArray(IndexTag.RPMTAG_PROVIDEFLAGS); + var names = this.GetStringArray(IndexTag.RPMTAG_PROVIDENAME); + var vers = this.GetStringArray(IndexTag.RPMTAG_PROVIDEVERSION); + + for (int i = 0; i < flags.Count; i++) + { + yield return new PackageDependency() + { + Flags = (RpmSense)flags[i], + Name = names[i], + Version = vers[i] + }; + } + } + + set + { + var provides = value.ToArray(); + + var flags = new int[provides.Length]; + var names = new string[provides.Length]; + var vers = new string[provides.Length]; + + for (int i = 0; i < provides.Length; i++) + { + flags[i] = (int)provides[i].Flags; + names[i] = provides[i].Name; + vers[i] = provides[i].Version; + } + + this.SetIntArray(IndexTag.RPMTAG_PROVIDEFLAGS, flags); + this.SetStringArray(IndexTag.RPMTAG_PROVIDENAME, names); + this.SetStringArray(IndexTag.RPMTAG_PROVIDEVERSION, vers); + } + } + + protected char GetDependencyType(IndexTag tag) + { + switch (tag) + { + case IndexTag.RPMTAG_PROVIDENAME: + return 'P'; + case IndexTag.RPMTAG_REQUIRENAME: + return 'R'; + case IndexTag.RPMTAG_RECOMMENDNAME: + return 'r'; + case IndexTag.RPMTAG_SUGGESTNAME: + return 'S'; + case IndexTag.RPMTAG_SUPPLEMENTNAME: + return 'S'; + case IndexTag.RPMTAG_ENHANCENAME: + return 'e'; + case IndexTag.RPMTAG_CONFLICTNAME: + return 'C'; + case IndexTag.RPMTAG_OBSOLETENAME: + return 'O'; + default: + throw new ArgumentOutOfRangeException(nameof(tag)); + } + } + + protected IndexTag GetDependencyTag(char deptype) + { + switch (deptype) + { + case 'P': + return IndexTag.RPMTAG_PROVIDENAME; + case 'R': + return IndexTag.RPMTAG_REQUIRENAME; + case 'r': + return IndexTag.RPMTAG_RECOMMENDNAME; + case 's': + return IndexTag.RPMTAG_SUGGESTNAME; + case 'S': + return IndexTag.RPMTAG_SUPPLEMENTNAME; + case 'e': + return IndexTag.RPMTAG_ENHANCENAME; + case 'C': + return IndexTag.RPMTAG_CONFLICTNAME; + case 'O': + return IndexTag.RPMTAG_OBSOLETENAME; + default: + return IndexTag.RPMTAG_NOT_FOUND; + } + } + + protected Collection GetStringArray(IndexTag tag) + { + var value = this.GetValue>(tag, IndexType.RPM_STRING_ARRAY_TYPE); + + if (value == null) + { + return new Collection(); + } + else + { + return value; + } + } + + protected void SetStringArray(IndexTag tag, string[] value) + { + this.SetValue(tag, IndexType.RPM_STRING_ARRAY_TYPE, value); + } + + protected string GetLocalizedString(IndexTag tag) + { + var localizedValues = this.GetValue>(tag, IndexType.RPM_I18NSTRING_TYPE); + + return localizedValues.First(); + } + + protected void SetLocalizedString(IndexTag tag, string value) + { + if (this.Locales.Count == 0) + { + throw new InvalidOperationException(); + } + + var values = new string[this.Locales.Count]; + + for (int i = 0; i < values.Length; i++) + { + values[i] = value; + } + + this.SetValue(tag, IndexType.RPM_I18NSTRING_TYPE, values); + } + + protected string GetString(IndexTag tag) + { + return this.GetValue(tag, IndexType.RPM_STRING_TYPE); + } + + protected void SetString(IndexTag tag, string value) + { + this.SetSingleValue(tag, IndexType.RPM_STRING_TYPE, value); + } + + protected int GetInt(IndexTag tag) + { + return this.GetValue(tag, IndexType.RPM_INT32_TYPE); + } + + protected void SetInt(IndexTag tag, int value) + { + this.SetSingleValue(tag, IndexType.RPM_INT32_TYPE, value); + } + + protected Collection GetIntArray(IndexTag tag) + { + var value = this.GetValue>(tag, IndexType.RPM_INT32_TYPE); + + if (value == null) + { + return new Collection(); + } + else + { + return value; + } + } + + protected void SetIntArray(IndexTag tag, int[] value) + { + this.SetValue(tag, IndexType.RPM_INT32_TYPE, value); + } + + protected Collection GetShortArray(IndexTag tag) + { + return this.GetValue>(tag, IndexType.RPM_INT16_TYPE); + } + + protected void SetShortArray(IndexTag tag, short[] value) + { + this.SetValue(tag, IndexType.RPM_INT16_TYPE, value); + } + + protected byte[] GetByteArray(IndexTag tag) + { + return this.GetValue(tag, IndexType.RPM_BIN_TYPE); + } + + protected void SetByteArray(IndexTag tag, byte[] value) + { + this.SetValue(tag, IndexType.RPM_BIN_TYPE, value); + } + + protected T GetValue(IndexTag tag, IndexType type) + { + if (!this.Package.Header.Records.ContainsKey(tag)) + { + return default(T); + } + + var record = this.Package.Header.Records[tag]; + + if (record.Header.Type != type) + { + throw new ArgumentOutOfRangeException(nameof(tag)); + } + + return (T)record.Value; + } + + protected void SetValue(IndexTag tag, IndexType type, T[] value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + // We won't accept empty arrays; rather, we remove the key alltogether. + // This brings compatibility with newer versions of RPM + if (value.Length > 0) + { + var collection = new Collection(value); + + IndexRecord record = new IndexRecord() + { + Header = new IndexHeader() + { + Count = collection.Count, + Offset = 0, + Tag = (uint)tag, + Type = type + }, + Value = collection + }; + + if (!this.Package.Header.Records.ContainsKey(tag)) + { + this.Package.Header.Records.Add(tag, record); + } + else + { + this.Package.Header.Records[tag] = record; + } + } + else + { + if (this.Package.Header.Records.ContainsKey(tag)) + { + this.Package.Header.Records.Remove(tag); + } + } + } + + protected void SetSingleValue(IndexTag tag, IndexType type, T value) + { + IndexRecord record = new IndexRecord() + { + Header = new IndexHeader() + { + Count = 1, + Offset = 0, + Tag = (uint)tag, + Type = type + }, + Value = value + }; + + if (!this.Package.Header.Records.ContainsKey(tag)) + { + this.Package.Header.Records.Add(tag, record); + } + else + { + this.Package.Header.Records[tag] = record; + } + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmPackage.cs b/DebUOS/Packaging.Targets/Rpm/RpmPackage.cs new file mode 100644 index 0000000..017351e --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmPackage.cs @@ -0,0 +1,49 @@ +using System.IO; + +namespace Packaging.Targets.Rpm +{ + internal class RpmPackage + { + public RpmPackage() + { + this.Header = new Section(); + this.Signature = new Section(); + } + + public RpmLead Lead + { + get; + set; + } + + public Section Header + { + get; + set; + } + + public Section Signature + { + get; + set; + } + + public long HeaderOffset + { + get; + set; + } + + public long PayloadOffset + { + get; + set; + } + + public Stream Stream + { + get; + set; + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmPackageCreator.cs b/DebUOS/Packaging.Targets/Rpm/RpmPackageCreator.cs new file mode 100644 index 0000000..9184fa8 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmPackageCreator.cs @@ -0,0 +1,949 @@ +using DiscUtils.Internal; +using Org.BouncyCastle.Bcpg.OpenPgp; +using Packaging.Targets.IO; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; + +namespace Packaging.Targets.Rpm +{ + /// + /// Supports creating objects based on a . + /// + internal class RpmPackageCreator + { + /// + /// The which analyzes the files in this package and provides required + /// metadata for the files. + /// + private readonly IFileAnalyzer analyzer; + + /// + /// Initializes a new instance of the class. + /// + public RpmPackageCreator() + : this(new FileAnalyzer()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// An used to analyze the files in this package and provides required + /// meatata for the files. + /// + public RpmPackageCreator(IFileAnalyzer analyzer) + { + if (analyzer == null) + { + throw new ArgumentNullException(nameof(analyzer)); + } + + this.analyzer = analyzer; + } + + /// + /// Gets or sets the number of threads to use when compressing .xz files. + /// The default is to use the number of CPUs. You may want to set this to 1 for + /// compatibility purposes. + /// + public int CompressionThreads { get; set; } = XZOutputStream.DefaultThreads; + + /// + /// Creates a RPM Package. + /// + /// + /// The archive entries which make up the RPM package. + /// + /// + /// A which contains the CPIO archive for the RPM package. + /// + /// + /// The name of the package. + /// + /// + /// The version of the software. + /// + /// + /// The architecture targetted by the package. + /// + /// + /// The release version. + /// + /// + /// to create a user account; otherwise, . + /// + /// + /// The name of the user account to create. + /// + /// + /// to install a system service, otherwise, . + /// + /// + /// The name of the system service to create. + /// + /// + /// The package vendor. + /// + /// + /// The package description. + /// + /// + /// The package URL. + /// + /// + /// A prefix to use. + /// + /// + /// Pre-Install script + /// + /// + /// Post-Install script + /// + /// + /// Pre-Remove script + /// + /// + /// Post-Remove script + /// + /// + /// Additional dependencies to add to the RPM package. + /// + /// + /// Any additional metadata. + /// + /// + /// The private key to use when signing the package. + /// + /// + /// The to which to write the package. + /// + public void CreatePackage( + List archiveEntries, + Stream payloadStream, + string name, + string version, + string arch, + string release, + bool createUser, + string userName, + bool installService, + string serviceName, + string vendor, + string description, + string url, + string prefix, + string preInstallScript, + string postInstallScript, + string preRemoveScript, + string postRemoveScript, + IEnumerable additionalDependencies, + Action additionalMetadata, + PgpPrivateKey privateKey, + Stream targetStream) + { + this.CreatePackage( + archiveEntries, + payloadStream, + name, + version, + arch, + release, + createUser, + userName, + installService, + serviceName, + vendor, + description, + url, + prefix, + preInstallScript, + postInstallScript, + preRemoveScript, + postRemoveScript, + additionalDependencies, + additionalMetadata, + new PackageSigner(privateKey), + targetStream); + } + + /// + /// Creates a RPM Package. + /// + /// + /// The archive entries which make up the RPM package. + /// + /// + /// A which contains the CPIO archive for the RPM package. + /// + /// + /// The name of the package. + /// + /// + /// The version of the software. + /// + /// + /// The architecture targetted by the package. + /// + /// + /// The release version. + /// + /// + /// to create a user account; otherwise, . + /// + /// + /// The name of the user account to create. + /// + /// + /// to install a system service, otherwise, . + /// + /// + /// The name of the system service to create. + /// + /// + /// The package vendor. + /// + /// + /// The package description. + /// + /// + /// The package URL. + /// + /// + /// A prefix to use. + /// + /// + /// Pre-Install script + /// + /// + /// Post-Install script + /// + /// + /// Pre-Remove script + /// + /// + /// Post-Remove script + /// + /// + /// Additional dependencies to add to the RPM package. + /// + /// + /// Any additional metadata. + /// + /// + /// The signer to use when signing the package. + /// + /// + /// The to which to write the package. + /// + /// + /// to include the version number and release number + /// in the ; to only + /// use the package name. + /// + /// + /// if is already + /// compressed. In this case, the will be + /// copied "as is" to the resulting RPM package. + /// + public void CreatePackage( + List archiveEntries, + Stream payloadStream, + string name, + string version, + string arch, + string release, + bool createUser, + string userName, + bool installService, + string serviceName, + string vendor, + string description, + string url, + string prefix, + string preInstallScript, + string postInstallScript, + string preRemoveScript, + string postRemoveScript, + IEnumerable additionalDependencies, + Action additionalMetadata, + IPackageSigner signer, + Stream targetStream, + bool includeVersionInName = false, + bool payloadIsCompressed = false) + { + // This routine goes roughly like: + // 1. Calculate all the metadata, including a signature, + // but use an empty compressed payload to calculate + // the signature + // 2. Write out the rpm file, and compress the payload + // 3. Update the signature + // + // This way, we avoid having to compress the payload into a temporary + // file. + + // Core routine to populate files and dependencies (part of the metadata + // in the header) + RpmPackage package = new RpmPackage(); + var metadata = new RpmMetadata(package) + { + Name = name, + Version = version, + Arch = arch, + Release = release, + }; + + this.AddPackageProvides(metadata); + this.AddLdDependencies(metadata); + + var files = this.CreateFiles(archiveEntries); + metadata.Files = files; + + this.AddRpmDependencies(metadata, additionalDependencies); + + // Try to define valid defaults for most metadata + metadata.Locales = new Collection { "C" }; // Should come before any localizable data. + metadata.BuildHost = "dotnet-rpm"; + metadata.BuildTime = DateTimeOffset.Now; + metadata.Cookie = "dotnet-rpm"; + metadata.FileDigetsAlgo = PgpHashAlgo.PGPHASHALGO_SHA256; + metadata.Group = "System Environment/Libraries"; + metadata.OptFlags = string.Empty; + metadata.Os = "linux"; + metadata.PayloadCompressor = "xz"; + metadata.PayloadFlags = "2"; + metadata.PayloadFormat = "cpio"; + metadata.Platform = "x86_64-redhat-linux-gnu"; + metadata.RpmVersion = "4.11.3"; + metadata.SourcePkgId = new byte[0x10]; + metadata.SourceRpm = $"{name}-{version}-{release}.src.rpm"; + + // Scripts which run before & after installation and removal. + var preIn = preInstallScript ?? string.Empty; + var postIn = postInstallScript ?? string.Empty; + var preUn = preRemoveScript ?? string.Empty; + var postUn = postRemoveScript ?? string.Empty; + + if (createUser) + { + // Add the user and group, under which the service runs. + // These users are never removed because UIDs are re-used on Linux. + preIn += $"/usr/sbin/groupadd -r {userName} 2>/dev/null || :\n" + + $"/usr/sbin/useradd -g {userName} -s /sbin/nologin -r {userName} 2>/dev/null || :\n"; + } + + if (installService) + { + // Install and activate the service. + postIn += + $"if [ $1 -eq 1 ] ; then \n" + + $" systemctl enable --now {serviceName}.service >/dev/null 2>&1 || : \n" + + $"fi\n"; + + preUn += + $"if [ $1 -eq 0 ] ; then \n" + + $" # Package removal, not upgrade \n" + + $" systemctl --no-reload disable --now {serviceName}.service > /dev/null 2>&1 || : \n" + + $"fi\n"; + + postUn += + $"if [ $1 -ge 1 ] ; then \n" + + $" # Package upgrade, not uninstall \n" + + $" systemctl try-restart {serviceName}.service >/dev/null 2>&1 || : \n" + + $"fi\n"; + } + + // Remove all directories marked as such (these are usually directories which contain temporary files) + foreach (var entryToRemove in archiveEntries.Where(e => e.RemoveOnUninstall)) + { + preUn += + $"if [ $1 -eq 0 ] ; then \n" + + $" # Package removal, not upgrade \n" + + $" /usr/bin/rm -rf {entryToRemove.TargetPath}\n" + + $"fi\n"; + } + + if (!string.IsNullOrEmpty(preIn)) + { + metadata.PreInProg = "/bin/sh"; + metadata.PreIn = preIn; + } + + if (!string.IsNullOrEmpty(postIn)) + { + metadata.PostInProg = "/bin/sh"; + metadata.PostIn = postIn; + } + + if (!string.IsNullOrEmpty(preUn)) + { + metadata.PreUnProg = "/bin/sh"; + metadata.PreUn = preUn; + } + + if (!string.IsNullOrEmpty(postUn)) + { + metadata.PostUnProg = "/bin/sh"; + metadata.PostUn = postUn; + } + + // Not providing these (or setting empty values) would cause rpmlint errors + metadata.Description = string.IsNullOrEmpty(description) + ? $"{name} version {version}-{release}" + : description; + metadata.Summary = $"{name} version {version}-{release}"; + metadata.License = $"{name} License"; + + metadata.Distribution = string.Empty; + metadata.DistUrl = string.Empty; + metadata.Url = url ?? string.Empty; + metadata.Vendor = vendor ?? string.Empty; + + metadata.ChangelogEntries = new Collection() + { + new ChangelogEntry(DateTimeOffset.Now, "dotnet-rpm", "Created a RPM package using dotnet-rpm") + }; + + // User-set metadata + if (additionalMetadata != null) + { + additionalMetadata(metadata); + } + + this.CalculateHeaderOffsets(package); + + using (MemoryStream dummyCompressedPayload = new MemoryStream()) + { + using (XZOutputStream dummyPayloadCompressor = + new XZOutputStream( + dummyCompressedPayload, + this.CompressionThreads, + XZOutputStream.DefaultPreset, + leaveOpen: true)) + { + dummyPayloadCompressor.Write(new byte[] { 0 }, 0, 1); + } + + this.CalculateSignature(package, signer, dummyCompressedPayload); + } + + this.CalculateSignatureOffsets(package); + + // Write out all the data - includes the lead + byte[] nameBytes = new byte[66]; + var nameInLead = includeVersionInName ? $"{name}-{version}-{release}" : name; + + Encoding.UTF8.GetBytes(nameInLead, 0, nameInLead.Length, nameBytes, 0); + + var lead = new RpmLead() + { + ArchNum = 1, + Magic = 0xedabeedb, + Major = 0x03, + Minor = 0x00, + NameBytes = nameBytes, + OsNum = 0x0001, + Reserved = new byte[16], + SignatureType = 0x0005, + Type = 0x0000, + }; + + // Write out the lead, signature and header + targetStream.Position = 0; + targetStream.SetLength(0); + + targetStream.WriteStruct(lead); + this.WriteSignature(package, targetStream); + this.WriteHeader(package, targetStream); + + // Write out the compressed payload + int compressedPayloadOffset = (int)targetStream.Position; + + // The user can choose to pass an already-comrpessed + // payload. In this case, no need to re-compress. + if (payloadIsCompressed) + { + payloadStream.CopyTo(targetStream); + } + else + { + using (XZOutputStream compressor = new XZOutputStream( + targetStream, + XZOutputStream.DefaultThreads, + XZOutputStream.DefaultPreset, + leaveOpen: true)) + { + payloadStream.Position = 0; + payloadStream.CopyTo(compressor); + } + } + + using (SubStream compressedPayloadStream = new SubStream( + targetStream, + compressedPayloadOffset, + targetStream.Length - compressedPayloadOffset, + leaveParentOpen: true, + readOnly: true)) + { + this.CalculateSignature(package, signer, compressedPayloadStream); + this.CalculateSignatureOffsets(package); + } + + // Update the lead and signature + targetStream.Position = 0; + + targetStream.WriteStruct(lead); + this.WriteSignature(package, targetStream); + } + + /// + /// Creates the metadata for all files in the . + /// + /// + /// The archive entries for which to generate the metadata. + /// + /// + /// A which contains all the metadata. + /// + public Collection CreateFiles(List archiveEntries) + { + Collection files = new Collection(); + + foreach (var entry in archiveEntries) + { + var size = entry.FileSize; + + if (entry.Mode.HasFlag(LinuxFileMode.S_IFDIR)) + { + 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, + Mode = entry.Mode, + Rdev = 0, + ModifiedTime = entry.Modified, + MD5Hash = entry.Sha256, // Yes, the MD5 hash does not actually contain a MD5 hash + LinkTo = entry.LinkTo, + Flags = this.analyzer.DetermineFlags(entry), + UserName = entry.Owner, + GroupName = entry.Group, + VerifyFlags = RpmVerifyFlags.RPMVERIFY_ALL, + Device = 1, + Inode = (int)entry.Inode, + Lang = string.Empty, + Color = this.analyzer.DetermineColor(entry), + Class = this.analyzer.DetermineClass(entry), + Requires = this.analyzer.DetermineRequires(entry), + Provides = this.analyzer.DetermineProvides(entry), + Name = entry.TargetPath + }; + + files.Add(file); + } + + return files; + } + + /// + /// Adds the package-level provides to the metadata. These are basically statements + /// indicating that the package provides, well, itself. + /// + /// + /// The package to which to add the provides. + /// + public void AddPackageProvides(RpmMetadata metadata) + { + var provides = metadata.Provides.ToList(); + + var packageProvides = new PackageDependency(metadata.Name, RpmSense.RPMSENSE_EQUAL, $"{metadata.Version}-{metadata.Release}"); + + var normalizedArch = metadata.Arch; + if (normalizedArch == "x86_64") + { + normalizedArch = "x86-64"; + } + + var packageArchProvides = new PackageDependency($"{metadata.Name}({normalizedArch})", RpmSense.RPMSENSE_EQUAL, $"{metadata.Version}-{metadata.Release}"); + + if (!provides.Contains(packageProvides)) + { + provides.Add(packageProvides); + } + + if (!provides.Contains(packageArchProvides)) + { + provides.Add(packageArchProvides); + } + + metadata.Provides = provides; + } + + /// + /// Adds the dependency on ld to the RPM package. These dependencies cause ldconfig to run post installation + /// and uninstallation of the RPM package. + /// + /// + /// The to which to add the dependencies. + /// + public void AddLdDependencies(RpmMetadata metadata) + { + Collection ldDependencies = new Collection() + { + new PackageDependency("/sbin/ldconfig", RpmSense.RPMSENSE_INTERP | RpmSense.RPMSENSE_SCRIPT_POST, string.Empty), + new PackageDependency("/sbin/ldconfig", RpmSense.RPMSENSE_INTERP | RpmSense.RPMSENSE_SCRIPT_POSTUN, string.Empty) + }; + + var dependencies = metadata.Dependencies.ToList(); + dependencies.AddRange(ldDependencies); + metadata.Dependencies = dependencies; + } + + /// + /// Adds the RPM dependencies to the package. These dependencies express dependencies on specific RPM features, such as compressed file names, + /// file digets, and xz-compressed payloads. + /// + /// + /// The to which to add the dependencies. + /// + public void AddRpmDependencies(RpmMetadata metadata, IEnumerable additionalDependencies) + { + // Somehow, three rpmlib dependencies come before the rtld(GNU_HASH) dependency and one after. + // The rtld(GNU_HASH) indicates that hashes are stored in the .gnu_hash instead of the .hash section + // in the ELF file, so it is a file-level dependency that bubbles up + // http://lists.rpm.org/pipermail/rpm-maint/2014-September/003764.html + // 9:34 PM 10/6/2017 + // To work around it, we remove the rtld(GNU_HASH) dependency on the files, remove it as a dependency, + // and add it back once we're done. + // + // The sole purpose of this is to ensure binary compatibility, which is probably not required at runtime, + // but makes certain regression tests more stable. + // + // Here we go: + var files = metadata.Files.ToArray(); + + var gnuHashFiles = + files + .Where(f => f.Requires.Any(r => string.Equals(r.Name, "rtld(GNU_HASH)", StringComparison.Ordinal))) + .ToArray(); + + foreach (var file in gnuHashFiles) + { + var rtldDependency = file.Requires.Where(r => string.Equals(r.Name, "rtld(GNU_HASH)", StringComparison.Ordinal)).Single(); + file.Requires.Remove(rtldDependency); + } + + // Refresh + metadata.Files = files; + + Collection rpmDependencies = new Collection() + { + new PackageDependency("rpmlib(CompressedFileNames)", RpmSense.RPMSENSE_LESS | RpmSense.RPMSENSE_EQUAL | RpmSense.RPMSENSE_RPMLIB, "3.0.4-1"), + new PackageDependency("rpmlib(FileDigests)", RpmSense.RPMSENSE_LESS | RpmSense.RPMSENSE_EQUAL | RpmSense.RPMSENSE_RPMLIB, "4.6.0-1"), + new PackageDependency("rpmlib(PayloadFilesHavePrefix)", RpmSense.RPMSENSE_LESS | RpmSense.RPMSENSE_EQUAL | RpmSense.RPMSENSE_RPMLIB, "4.0-1"), + new PackageDependency("rtld(GNU_HASH)", RpmSense.RPMSENSE_FIND_REQUIRES, string.Empty), + }; + + // Inject any additional dependencies the user may have specified. + if (additionalDependencies != null) + { + rpmDependencies.AddRange(additionalDependencies); + } + + rpmDependencies.Add(new PackageDependency("rpmlib(PayloadIsXz)", RpmSense.RPMSENSE_LESS | RpmSense.RPMSENSE_EQUAL | RpmSense.RPMSENSE_RPMLIB, "5.2-1")); + + var dependencies = metadata.Dependencies.ToList(); + var last = dependencies.Last(); + + if (last.Name == "rtld(GNU_HASH)") + { + dependencies.Remove(last); + } + + dependencies.AddRange(rpmDependencies); + metadata.Dependencies = dependencies; + + // Add the rtld(GNU_HASH) dependency back to the files + foreach (var file in gnuHashFiles) + { + file.Requires.Add(new PackageDependency("rtld(GNU_HASH)", RpmSense.RPMSENSE_FIND_REQUIRES, string.Empty)); + } + + // Refresh + metadata.Files = files; + } + + /// + /// Determines the offsets for all records in the header of a package. + /// + /// + /// The package for which to generate the offsets. + /// + public void CalculateHeaderOffsets(RpmPackage package) + { + var metadata = new RpmMetadata(package); + metadata.ImmutableRegionSize = -1 * Marshal.SizeOf() * (package.Header.Records.Count + 1); + + this.CalculateSectionOffsets(package.Header, k => (int)k); + } + + /// + /// Determines the offsets for all records in the signature of a package. + /// + /// + /// The package for which to generate the offsets. + /// + public void CalculateSignatureOffsets(RpmPackage package) + { + var signature = new RpmSignature(package); + signature.ImmutableRegionSize = -1 * Marshal.SizeOf() * package.Signature.Records.Count; + + this.CalculateSectionOffsets(package.Signature, k => (int)k); + } + + /// + /// Gets a wich represents the entire header. + /// + /// + /// The package for which to get the header stream. + /// + /// + /// A wich represents the entire header. + /// + public MemoryStream GetHeaderStream(RpmPackage package) + { + MemoryStream stream = new MemoryStream(); + this.WriteHeader(package, stream); + stream.Position = 0; + return stream; + } + + /// + /// Writes the header to a . + /// + /// + /// The package for which to write the header. + /// + /// + /// The to which to write the header. + /// + public void WriteHeader(RpmPackage package, Stream targetStream) + { + RpmPackageWriter.WriteSection(targetStream, package.Header, DefaultOrder.Header); + } + + /// + /// Gets a wich represents the entire signature. + /// + /// + /// The package for which to get the header stream. + /// + /// + /// A wich represents the entire signature. + /// + public MemoryStream GetSignatureStream(RpmPackage package) + { + MemoryStream stream = new MemoryStream(); + this.WriteSignature(package, stream); + stream.Position = 0; + return stream; + } + + /// + /// Writes the signature to a . + /// + /// + /// The package for which to write the signature. + /// + /// + /// The tow chih to write the package. + /// + public void WriteSignature(RpmPackage package, Stream targetStream) + { + RpmPackageWriter.WriteSection(targetStream, package.Signature, DefaultOrder.Signature); + } + + /// + /// Calculates the signature for this package. + /// + /// + /// The package for whcih to calculate the signature. + /// + /// + /// The private key to use when signing packages. + /// + /// + /// The compressed payload. + /// + public void CalculateSignature(RpmPackage package, PgpPrivateKey privateKey, Stream compressedPayloadStream) + { + this.CalculateSignature(package, new PackageSigner(privateKey), compressedPayloadStream); + } + + /// + /// Calculates the signature for this package. + /// + /// + /// The package for whcih to calculate the signature. + /// + /// + /// The signer to use. + /// + /// + /// The compressed payload. + /// + public void CalculateSignature(RpmPackage package, IPackageSigner signer, Stream compressedPayloadStream) + { + RpmSignature signature = new RpmSignature(package); + + using (MemoryStream headerStream = this.GetHeaderStream(package)) + using (ConcatStream headerAndPayloadStream = new ConcatStream(leaveOpen: true, streams: new Stream[] { headerStream, compressedPayloadStream })) + { + SHA1 sha = SHA1.Create(); + signature.Sha1Hash = sha.ComputeHash(headerStream); + + MD5 md5 = MD5.Create(); + signature.MD5Hash = md5.ComputeHash(headerAndPayloadStream); + + // Verify the PGP signatures + // 3 for the header + headerStream.Position = 0; + signature.HeaderPgpSignature = signer.Sign(headerStream); + + headerAndPayloadStream.Position = 0; + signature.HeaderAndPayloadPgpSignature = signer.Sign(headerAndPayloadStream); + + // Verify the signature size (header + compressed payload) + signature.HeaderAndPayloadSize = (int)headerAndPayloadStream.Length; + } + + // Verify the payload size (header + uncompressed payload) + using (Stream payloadStream = RpmPayloadReader.GetDecompressedPayloadStream(package, compressedPayloadStream)) + { + signature.UncompressedPayloadSize = (int)payloadStream.Length; + } + } + + protected void CalculateSectionOffsets(Section section, Func getSortOrder) + { + var records = section.Records; + int offset = 0; + + T immutableKey = default(T); + + var sortedRecords = records.OrderBy(r => getSortOrder(r.Key)).ToArray(); + + foreach (var record in sortedRecords) + { + var indexTag = record.Key as IndexTag?; + var signatureTag = record.Key as SignatureTag?; + + if (indexTag == IndexTag.RPMTAG_HEADERIMMUTABLE + || signatureTag == SignatureTag.RPMTAG_HEADERSIGNATURES) + { + immutableKey = record.Key; + continue; + } + + var header = record.Value.Header; + + // Determine the size + int size = 0; + int align = 0; + + switch (header.Type) + { + case IndexType.RPM_BIN_TYPE: + case IndexType.RPM_CHAR_TYPE: + case IndexType.RPM_INT8_TYPE: + // These are all single-byte types + size = header.Count; + break; + + case IndexType.RPM_I18NSTRING_TYPE: + case IndexType.RPM_STRING_ARRAY_TYPE: + foreach (var value in (IEnumerable)record.Value.Value) + { + if (value != null) + { + size += Encoding.UTF8.GetByteCount(value) + 1; + } + else + { + size += 1; + } + } + + break; + + case IndexType.RPM_STRING_TYPE: + size = Encoding.UTF8.GetByteCount((string)record.Value.Value) + 1; + break; + + case IndexType.RPM_INT16_TYPE: + size = 2 * header.Count; + break; + + case IndexType.RPM_INT32_TYPE: + size = 4 * header.Count; + align = 4; + break; + + case IndexType.RPM_INT64_TYPE: + size = 8 * header.Count; + break; + + case IndexType.RPM_NULL_TYPE: + size = 0; + break; + } + + if (align != 0 && offset % align != 0) + { + offset += align - (offset % align); + } + + header.Offset = offset; + offset += size; + + record.Value.Header = header; + } + + if (!object.Equals(immutableKey, default(T))) + { + var record = records[immutableKey]; + var header = record.Header; + header.Offset = offset; + record.Header = header; + + offset += Marshal.SizeOf(); + } + + // This is also a good time to refresh the header + section.Header = new RpmHeader() + { + HeaderSize = (uint)offset, + IndexCount = (uint)section.Records.Count, + Magic = 0x8eade801, + Reserved = 0 + }; + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmPackageReader.cs b/DebUOS/Packaging.Targets/Rpm/RpmPackageReader.cs new file mode 100644 index 0000000..24d519f --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmPackageReader.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace Packaging.Targets.Rpm +{ + internal class RpmPackageReader + { + public static RpmPackage Read(Stream stream) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!stream.CanSeek) + { + throw new ArgumentOutOfRangeException(nameof(stream), "A stream which backs a RPM package must be seekable"); + } + + RpmPackage package = new RpmPackage(); + package.Stream = stream; + + package.Lead = stream.ReadStruct(); + + package.Signature = ReadSection(stream, h => (SignatureTag)h.Tag); + + if (stream.Position % 8 != 0) + { + stream.Position += 8 - (stream.Position % 8); + } + + package.HeaderOffset = stream.Position; + package.Header = ReadSection(stream, h => (IndexTag)h.Tag); + + package.PayloadOffset = stream.Position; + + return package; + } + + public static Section ReadSection(Stream stream, Func getTag) + { + Section section = new Section(); + + section.Header = stream.ReadStruct(); + + for (int i = 0; i < section.Header.IndexCount; i++) + { + var header = stream.ReadStruct(); + section.Records.Add( + getTag(header), + new IndexRecord() + { + Header = header + }); + } + + var data = new byte[section.Header.HeaderSize]; + stream.Read(data, 0, (int)section.Header.HeaderSize); + + var headerSize = Marshal.SizeOf() * section.Records.Count; + + byte[] int16Buffer = new byte[2]; + byte[] int32Buffer = new byte[4]; + byte[] int64Buffer = new byte[8]; + + // Read the data for all records + foreach (var record in section.Records.Values) + { + var offset = (int)record.Header.Offset; + + switch (record.Header.Type) + { + case IndexType.RPM_CHAR_TYPE: + record.Value = (char)data[offset]; + break; + + case IndexType.RPM_INT8_TYPE: + record.Value = (byte)data[offset]; + break; + + case IndexType.RPM_INT16_TYPE: + Collection shortValues = new Collection(); + + for (int i = 0; i < record.Header.Count; i++) + { + Array.Copy(data, offset + i * sizeof(short), int16Buffer, 0, sizeof(short)); + Array.Reverse(int16Buffer); + shortValues.Add(BitConverter.ToInt16(int16Buffer, 0)); + } + + if (shortValues.Count == 1) + { + record.Value = shortValues[0]; + } + else + { + record.Value = shortValues; + } + + break; + + case IndexType.RPM_INT32_TYPE: + Collection intValues = new Collection(); + + for (int i = 0; i < record.Header.Count; i++) + { + Array.Copy(data, offset + i * sizeof(int), int32Buffer, 0, sizeof(int)); + Array.Reverse(int32Buffer); + intValues.Add(BitConverter.ToInt32(int32Buffer, 0)); + } + + if (intValues.Count == 1) + { + record.Value = intValues[0]; + } + else + { + record.Value = intValues; + } + + break; + + case IndexType.RPM_INT64_TYPE: + Collection longValues = new Collection(); + + for (int i = 0; i < record.Header.Count; i++) + { + Array.Copy(data, offset + i * sizeof(long), int64Buffer, 0, sizeof(long)); + Array.Reverse(int64Buffer); + longValues.Add(BitConverter.ToInt64(int64Buffer, 0)); + } + + if (longValues.Count == 1) + { + record.Value = longValues[0]; + } + else + { + record.Value = longValues; + } + + break; + + case IndexType.RPM_STRING_TYPE: + record.Value = ReadNullTerminatedString(ref offset, data); + break; + + case IndexType.RPM_I18NSTRING_TYPE: + case IndexType.RPM_STRING_ARRAY_TYPE: + Collection strings = new Collection(); + + for (int i = 0; i < record.Header.Count; i++) + { + strings.Add(ReadNullTerminatedString(ref offset, data)); + } + + record.Value = strings; + break; + + case IndexType.RPM_BIN_TYPE: + byte[] value = new byte[record.Header.Count]; + Array.Copy(data, offset, value, 0, (int)record.Header.Count); + record.Value = value; + break; + + case IndexType.RPM_NULL_TYPE: + default: + throw new ArgumentOutOfRangeException(); + } + } + + return section; + } + + private static string ReadNullTerminatedString(ref int offset, byte[] data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + int stringLength = 0; + + while (offset + stringLength < data.Length && data[offset + stringLength] != 0) + { + stringLength++; + } + + string value; + + if (stringLength > 0) + { + value = Encoding.UTF8.GetString(data, offset, stringLength); + } + else + { + value = string.Empty; + } + + offset += stringLength; + offset += 1; + return value; + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmPackageWriter.cs b/DebUOS/Packaging.Targets/Rpm/RpmPackageWriter.cs new file mode 100644 index 0000000..f966462 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmPackageWriter.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; + +namespace Packaging.Targets.Rpm +{ + /// + /// Provides methods which support writing a to a . + /// + internal class RpmPackageWriter + { + /// + /// Writes a to a . + /// + /// + /// The to which to write the . + /// + /// + /// The to write to the stream. + /// + public static void Write(Stream stream, RpmPackage package) + { + if (package == null) + { + throw new ArgumentNullException(nameof(package)); + } + + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + stream.WriteStruct(package.Lead); + + WriteSection(stream, package.Signature, DefaultOrder.Signature); + WriteSection(stream, package.Header, DefaultOrder.Header); + } + + public static void WriteSection(Stream stream, Section section, List order) + { + while (stream.Position % 8 != 0) + { + stream.WriteByte(0); + } + + stream.WriteStruct(section.Header); + + // First write out all records in the pre-defined order + foreach (var record in order) + { + if (section.Records.ContainsKey(record)) + { + stream.WriteStruct(section.Records[record].Header); + } + } + + // Then write out any record which may be new. + foreach (var record in section.Records) + { + if (!order.Contains(record.Key)) + { + stream.WriteStruct(record.Value.Header); + } + } + + // Write the data for all records + long start = stream.Position; + + var records = section.Records.Values.OrderBy(v => v.Header.Offset).ToArray(); + foreach (var record in records) + { + long actualOffset = stream.Position - start; + + if (actualOffset > record.Header.Offset) + { + throw new InvalidOperationException(); + } + + for (int i = 0; i < record.Header.Offset - actualOffset; i++) + { + stream.WriteByte(0); + } + + switch (record.Header.Type) + { + case IndexType.RPM_CHAR_TYPE: + stream.WriteByte((byte)record.Value); + break; + + case IndexType.RPM_INT8_TYPE: + stream.WriteByte((byte)record.Value); + break; + + case IndexType.RPM_INT16_TYPE: + if (record.Value is short) + { + stream.WriteBE((short)record.Value); + } + else + { + var shortValues = (IEnumerable)record.Value; + + foreach (var shortValue in shortValues) + { + stream.WriteBE(shortValue); + } + } + + break; + + case IndexType.RPM_INT32_TYPE: + if (record.Value is int) + { + stream.WriteBE((int)record.Value); + } + else + { + var shortValues = (IEnumerable)record.Value; + + foreach (var shortValue in shortValues) + { + stream.WriteBE(shortValue); + } + } + + break; + + case IndexType.RPM_INT64_TYPE: + if (record.Value is long) + { + stream.WriteBE((long)record.Value); + } + else + { + var longValues = (IEnumerable)record.Value; + + foreach (var shortValue in longValues) + { + stream.WriteBE(shortValue); + } + } + + break; + + case IndexType.RPM_STRING_TYPE: + WriteNullTerminatedString(stream, (string)record.Value); + break; + + case IndexType.RPM_I18NSTRING_TYPE: + case IndexType.RPM_STRING_ARRAY_TYPE: + Collection strings = (Collection)record.Value; + + foreach (var s in strings) + { + WriteNullTerminatedString(stream, s); + } + + break; + + case IndexType.RPM_BIN_TYPE: + var value = (IEnumerable)record.Value; + var array = value.ToArray(); + stream.Write(array, 0, array.Length); + break; + + case IndexType.RPM_NULL_TYPE: + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + private static void WriteNullTerminatedString(Stream stream, string value) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (value == null) + { + stream.WriteByte(0x00); + } + else + { + var length = Encoding.UTF8.GetByteCount(value) + 1; + byte[] data = new byte[length]; + Encoding.UTF8.GetBytes(value, 0, value.Length, data, 0); + + stream.Write(data, 0, data.Length); + } + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmPayloadReader.cs b/DebUOS/Packaging.Targets/Rpm/RpmPayloadReader.cs new file mode 100644 index 0000000..a2ad637 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmPayloadReader.cs @@ -0,0 +1,125 @@ +using Packaging.Targets.IO; +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; + +namespace Packaging.Targets.Rpm +{ + /// + /// Allows interacting with the payload of a RPM package. + /// + internal class RpmPayloadReader + { + public static Collection Read(RpmPackage package) + { + if (package == null) + { + throw new ArgumentNullException(nameof(package)); + } + + Collection names = new Collection(); + + using (var payloadDecompressedStream = GetDecompressedPayloadStream(package)) + using (CpioFile file = new CpioFile(payloadDecompressedStream, leaveOpen: true)) + { + while (file.Read()) + { + names.Add(file.FileName); + + using (Stream stream = file.Open()) + { + byte[] data = new byte[stream.Length]; + stream.Read(data, 0, data.Length); + } + } + } + + return names; + } + + /// + /// Gets the compressed payload for a package. + /// + /// + /// The package for which to get the compressed payload. + /// + /// + /// A which contains the compressed payload. + /// + public static Stream GetCompressedPayloadStream(RpmPackage package) + { + if (package == null) + { + throw new ArgumentNullException(nameof(package)); + } + + SubStream compressedPayloadStream = new SubStream( + stream: package.Stream, + offset: package.PayloadOffset, + length: package.Stream.Length - package.PayloadOffset, + leaveParentOpen: true, + readOnly: true); + + return compressedPayloadStream; + } + + /// + /// Gets a which allows reading the decompressed payload data. + /// + /// + /// The package for which to read the decompressed payload data. + /// + /// + /// A which allows reading the decompressed payload data. + /// + public static Stream GetDecompressedPayloadStream(RpmPackage package) + { + if (package == null) + { + throw new ArgumentNullException(nameof(package)); + } + + var compressedPayloadStream = GetCompressedPayloadStream(package); + + return GetDecompressedPayloadStream(package, compressedPayloadStream); + } + + /// + /// Gets a which allows reading the decompressed payload data. + /// + /// + /// The package for which to read the decompressed payload data. + /// + /// + /// The compressed payload stream. + /// + /// + /// A which allows reading the decompressed payload data. + /// + public static Stream GetDecompressedPayloadStream(RpmPackage package, Stream compressedPayloadStream) + { + var compressor = (string)package.Header.Records[IndexTag.RPMTAG_PAYLOADCOMPRESSOR].Value; + var payloadDecompressedStream = GetPayloadDecompressor(compressedPayloadStream, compressor); + + return payloadDecompressedStream; + } + + private static Stream GetPayloadDecompressor(Stream payloadStream, string compressor) + { + if (string.Equals(compressor, "gzip")) + { + return new GZipStream(payloadStream, CompressionMode.Decompress, leaveOpen: false); + } + else if (string.Equals(compressor, "xz")) + { + return new XZInputStream(payloadStream); + } + else + { + throw new ArgumentOutOfRangeException(nameof(compressor)); + } + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmSense.cs b/DebUOS/Packaging.Targets/Rpm/RpmSense.cs new file mode 100644 index 0000000..3eed735 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmSense.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Packaging.Targets.Rpm +{ + [Flags] + internal enum RpmSense + { + RPMSENSE_ANY = 0, + RPMSENSE_LESS = 1 << 1, + RPMSENSE_GREATER = 1 << 2, + RPMSENSE_EQUAL = 1 << 3, + RPMSENSE_POSTTRANS = 1 << 5, /*!< %posttrans dependency */ + RPMSENSE_PREREQ = 1 << 6, /* legacy prereq dependency */ + RPMSENSE_PRETRANS = 1 << 7, /*!< Pre-transaction dependency. */ + RPMSENSE_INTERP = 1 << 8, /*!< Interpreter used by scriptlet. */ + RPMSENSE_SCRIPT_PRE = 1 << 9, /*!< %pre dependency. */ + RPMSENSE_SCRIPT_POST = 1 << 10, /*!< %post dependency. */ + RPMSENSE_SCRIPT_PREUN = 1 << 11, /*!< %preun dependency. */ + RPMSENSE_SCRIPT_POSTUN = 1 << 12, /*!< %postun dependency. */ + RPMSENSE_SCRIPT_VERIFY = 1 << 13, /*!< %verify dependency. */ + RPMSENSE_FIND_REQUIRES = 1 << 14, /*!< find-requires generated dependency. */ + RPMSENSE_FIND_PROVIDES = 1 << 15, /*!< find-provides generated dependency. */ + + RPMSENSE_TRIGGERIN = 1 << 16, /*!< %triggerin dependency. */ + RPMSENSE_TRIGGERUN = 1 << 17, /*!< %triggerun dependency. */ + RPMSENSE_TRIGGERPOSTUN = 1 << 18, /*!< %triggerpostun dependency. */ + RPMSENSE_MISSINGOK = 1 << 19, /*!< suggests/enhances hint. */ + RPMSENSE_RPMLIB = 1 << 24, /*!< rpmlib(feature) dependency. */ + RPMSENSE_TRIGGERPREIN = 1 << 25, /*!< %triggerprein dependency. */ + RPMSENSE_KEYRING = 1 << 26, + RPMSENSE_CONFIG = 1 << 28 + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmSignature.cs b/DebUOS/Packaging.Targets/Rpm/RpmSignature.cs new file mode 100644 index 0000000..b291999 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmSignature.cs @@ -0,0 +1,372 @@ +using Org.BouncyCastle.Bcpg.OpenPgp; +using Packaging.Targets.IO; +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +namespace Packaging.Targets.Rpm +{ + /// + /// Enables interacting with the signature of a . + /// + internal class RpmSignature + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The package for which to access the signature. + /// + public RpmSignature(RpmPackage package) + { + if (package == null) + { + throw new ArgumentNullException(nameof(package)); + } + + this.Package = package; + } + + /// + /// Gets the package for which the signature is being inspected. + /// + public RpmPackage Package + { + get; + private set; + } + + /// + /// Gets or sets the length of the immutable region, starting from the position of the + /// record. This record should be the last record in the signature block, and the value is negative, indicating the previous + /// blocks are considered immutable. + /// + public int ImmutableRegionSize + { + get + { + // For more information about immutable regions, see: + // http://ftp.rpm.org/api/4.4.2.2/hregions.html + // https://dentrassi.de/2016/04/15/writing-rpm-files-in-plain-java/ + // https://blog.bethselamin.de/posts/argh-pm.html + // For now, we're always assuming the entire header and the entire signature is immutable + var immutableSignatureRegion = (byte[])this.Package.Signature.Records[SignatureTag.RPMTAG_HEADERSIGNATURES].Value; + + using (MemoryStream s = new MemoryStream(immutableSignatureRegion)) + { + var h = s.ReadStruct(); + return h.Offset; + } + } + + set + { + IndexHeader header = default(IndexHeader); + header.Offset = value; + header.Count = 0x10; + header.Type = IndexType.RPM_BIN_TYPE; + header.Tag = (uint)SignatureTag.RPMTAG_HEADERSIGNATURES; + + byte[] data; + + using (MemoryStream s = new MemoryStream()) + { + s.WriteStruct(header); + data = s.ToArray(); + } + + this.SetByteArray(SignatureTag.RPMTAG_HEADERSIGNATURES, data); + } + } + + /// + /// Gets or sets the SHA1 hash of the header section. + /// + public byte[] Sha1Hash + { + get { return StringToByteArray((string)this.Package.Signature.Records[SignatureTag.RPMSIGTAG_SHA1].Value); } + set { this.SetString(SignatureTag.RPMSIGTAG_SHA1, BitConverter.ToString(value).Replace("-", string.Empty).ToLower()); } + } + + /// + /// Gets or sets the MD5 hash of the header section and the compressed payload. + /// + public byte[] MD5Hash + { + get { return (byte[])this.Package.Signature.Records[SignatureTag.RPMSIGTAG_MD5].Value; } + set { this.SetByteArray(SignatureTag.RPMSIGTAG_MD5, value); } + } + + /// + /// Gets or sets the of the header section. + /// + public PgpSignature HeaderPgpSignature + { + get { return this.GetPgpSignature(SignatureTag.RPMSIGTAG_RSA); } + set { this.SetPgpSignature(SignatureTag.RPMSIGTAG_RSA, value); } + } + + /// + /// Gets or sets the of the header section and compressed payload. + /// + public PgpSignature HeaderAndPayloadPgpSignature + { + get { return this.GetPgpSignature(SignatureTag.RPMSIGTAG_PGP); } + set { this.SetPgpSignature(SignatureTag.RPMSIGTAG_PGP, value); } + } + + /// + /// Gets or sets the combined size of the header section and the compressed payload. + /// + public int HeaderAndPayloadSize + { + get { return (int)this.Package.Signature.Records[SignatureTag.RPMSIGTAG_SIZE].Value; } + set { this.SetInt(SignatureTag.RPMSIGTAG_SIZE, value); } + } + + /// + /// Gets or sets the size of the uncompressed payload. + /// + public int UncompressedPayloadSize + { + get { return (int)this.Package.Signature.Records[SignatureTag.RPMSIGTAG_PAYLOADSIZE].Value; } + set { this.SetInt(SignatureTag.RPMSIGTAG_PAYLOADSIZE, value); } + } + + /// + /// Verifys the signature of a RPM package. + /// + /// + /// A which contains the public key used to verify the data. + /// + /// + /// if the verification was successful; otherwise, . + /// + public bool Verify(Stream pgpPublicKeyStream) + { + var bundle = new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(pgpPublicKeyStream)); + var publicKeyRings = bundle.GetKeyRings().OfType(); + var publicKeys = publicKeyRings.SelectMany(x => x.GetPublicKeys().OfType()); + var publicKey = publicKeys.FirstOrDefault(); + + return this.Verify(publicKey); + } + + public bool Verify(PgpPublicKey pgpPublicKey) + { + // 1 Verify the header signature immutable block: make sure all records are marked as immutable + var immutableRegionSize = this.ImmutableRegionSize; + var immutableEntryCount = -immutableRegionSize / Marshal.SizeOf(); + + if (this.Package.Signature.Records.Count != immutableEntryCount) + { + return false; + } + + // Store the header data and the header + payload data in substreams. This enables us to calculate hashes and + // verify signatures using these data ranges. + using (Stream headerStream = new SubStream( + this.Package.Stream, + this.Package.HeaderOffset, + this.Package.PayloadOffset - this.Package.HeaderOffset, + leaveParentOpen: true, + readOnly: true)) + using (Stream headerAndPayloadStream = new SubStream( + this.Package.Stream, + this.Package.HeaderOffset, + this.Package.Stream.Length - this.Package.HeaderOffset, + leaveParentOpen: true, + readOnly: true)) + { + // Verify the SHA1 hash. This one covers the header block + SHA1 sha = SHA1.Create(); + var calculatedShaValue = sha.ComputeHash(headerStream); + var actualShaValue = this.Sha1Hash; + + if (!calculatedShaValue.SequenceEqual(actualShaValue)) + { + return false; + } + + // Verify the MD5 hash. This one covers the header and the payload block + MD5 md5 = MD5.Create(); + var calculatedMd5Value = md5.ComputeHash(headerAndPayloadStream); + var actualMd5Value = this.MD5Hash; + + if (!calculatedMd5Value.SequenceEqual(actualMd5Value)) + { + return false; + } + + // Verify the PGP signatures + // 3 for the header + var headerPgpSignature = this.HeaderPgpSignature; + headerStream.Position = 0; + + if (!PgpSigner.VerifySignature(headerPgpSignature, pgpPublicKey, headerStream)) + { + return false; + } + + var headerAndPayloadPgpSignature = this.HeaderAndPayloadPgpSignature; + headerAndPayloadStream.Position = 0; + + if (!PgpSigner.VerifySignature(headerAndPayloadPgpSignature, pgpPublicKey, headerAndPayloadStream)) + { + return false; + } + } + + // Verify the signature size (header + compressed payload) + var headerSize = this.HeaderAndPayloadSize; + + if (headerSize != this.Package.Stream.Length - this.Package.HeaderOffset) + { + return false; + } + + // Verify the payload size (header + uncompressed payload) + var expectedDecompressedPayloadSize = this.UncompressedPayloadSize; + + long actualDecompressedPayloadLength = 0; + + using (Stream payloadStream = RpmPayloadReader.GetDecompressedPayloadStream(this.Package)) + { + actualDecompressedPayloadLength = payloadStream.Length; + } + + if (expectedDecompressedPayloadSize != actualDecompressedPayloadLength) + { + return false; + } + + return true; + } + + /// + /// Converst a hexadecimal string to a array. + /// + /// + /// A containing the hexadecimal representation of the value. + /// + /// + /// A byte array representing the same value. + /// + internal static byte[] StringToByteArray(string hex) + { + int numberChars = hex.Length; + byte[] bytes = new byte[numberChars / 2]; + + for (int i = 0; i < numberChars; i += 2) + { + bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); + } + + return bytes; + } + + protected void SetInt(SignatureTag tag, int value) + { + this.SetSingleValue(tag, IndexType.RPM_INT32_TYPE, value); + } + + protected void SetString(SignatureTag tag, string value) + { + this.SetSingleValue(tag, IndexType.RPM_STRING_TYPE, value); + } + + protected void SetByteArray(SignatureTag tag, byte[] value) + { + this.SetValue(tag, IndexType.RPM_BIN_TYPE, value); + } + + protected void SetValue(SignatureTag tag, IndexType type, T[] value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + var collection = new Collection(value); + + IndexRecord record = new IndexRecord() + { + Header = new IndexHeader() + { + Count = collection.Count, + Offset = 0, + Tag = (uint)tag, + Type = type + }, + Value = collection + }; + + if (!this.Package.Signature.Records.ContainsKey(tag)) + { + this.Package.Signature.Records.Add(tag, record); + } + else + { + this.Package.Signature.Records[tag] = record; + } + } + + protected void SetSingleValue(SignatureTag tag, IndexType type, T value) + { + IndexRecord record = new IndexRecord() + { + Header = new IndexHeader() + { + Count = 1, + Offset = 0, + Tag = (uint)tag, + Type = type + }, + Value = value + }; + + if (!this.Package.Signature.Records.ContainsKey(tag)) + { + this.Package.Signature.Records.Add(tag, record); + } + else + { + this.Package.Signature.Records[tag] = record; + } + } + + /// + /// Gets a PGP signature. + /// + /// + /// The tag which contains the PGP signature. + /// + /// + /// A object which represents the PGP signature. + /// + private PgpSignature GetPgpSignature(SignatureTag tag) + { + byte[] value = (byte[])this.Package.Signature.Records[tag].Value; + + using (MemoryStream stream = new MemoryStream(value)) + using (var signatureStream = PgpUtilities.GetDecoderStream(stream)) + { + PgpObjectFactory pgpFactory = new PgpObjectFactory(signatureStream); + PgpSignatureList signatureList = (PgpSignatureList)pgpFactory.NextPgpObject(); + PgpSignature signature = signatureList[0]; + return signature; + } + } + + private void SetPgpSignature(SignatureTag tag, PgpSignature signature) + { + PgpSignatureList signatureList = new PgpSignatureList(signature); + byte[] value = signature.GetEncoded(); + + this.SetByteArray(tag, value); + } + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/RpmVerifyFlags.cs b/DebUOS/Packaging.Targets/Rpm/RpmVerifyFlags.cs new file mode 100644 index 0000000..01a12f0 --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/RpmVerifyFlags.cs @@ -0,0 +1,26 @@ +namespace Packaging.Targets.Rpm +{ + /// + /// Specifies how a file should be verified. + /// + internal enum RpmVerifyFlags + { + RPMVERIFY_NONE = 0, + RPMVERIFY_MD5 = 1 << 0, + RPMVERIFY_FILEDIGEST = 1 << 0, + RPMVERIFY_FILESIZE = 1 << 1, + RPMVERIFY_LINKTO = 1 << 2, + RPMVERIFY_USER = 1 << 3, + RPMVERIFY_GROUP = 1 << 4, + RPMVERIFY_MTIME = 1 << 5, + RPMVERIFY_MODE = 1 << 6, + RPMVERIFY_RDEV = 1 << 7, + RPMVERIFY_CAPS = 1 << 8, + RPMVERIFY_CONTEXTS = 1 << 15, + RPMVERIFY_READLINKFAIL = 1 << 28, + RPMVERIFY_READFAIL = 1 << 29, + RPMVERIFY_LSTATFAIL = 1 << 30, + RPMVERIFY_LGETFILECONFAIL = 1 << 31, + RPMVERIFY_ALL = ~RPMVERIFY_NONE + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/Section.cs b/DebUOS/Packaging.Targets/Rpm/Section.cs new file mode 100644 index 0000000..108a93f --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/Section.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Packaging.Targets.Rpm +{ + internal class Section + { + public RpmHeader Header + { + get; + set; + } + + public Dictionary Records + { get; set; } = new Dictionary(); + } +} diff --git a/DebUOS/Packaging.Targets/Rpm/SignatureTag.cs b/DebUOS/Packaging.Targets/Rpm/SignatureTag.cs new file mode 100644 index 0000000..b791bec --- /dev/null +++ b/DebUOS/Packaging.Targets/Rpm/SignatureTag.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Packaging.Targets.Rpm +{ + internal enum SignatureTag + { + /// + /// The signature tag differentiates a signature header from a metadata header, and identifies the original contents of the signature header. + /// + RPMTAG_HEADERSIGNATURES = 62, + + /// + /// This tag specifies the combined size of the Header and Payload sections. + /// + RPMSIGTAG_SIZE = 1000, + + /// + /// This tag specifies the uncompressed size of the Payload archive, including the cpio headers. + /// + RPMSIGTAG_PAYLOADSIZE = 1007, + + /// + /// This index contains the SHA1 checksum of the entire Header Section, including the Header Record, Index Records and Header store. + /// + RPMSIGTAG_SHA1 = 269, + + /// + /// This tag specifies the 128-bit MD5 checksum of the combined Header and Archive sections. + /// + RPMSIGTAG_MD5 = 1004, + + /// + /// The tag contains the DSA signature of the Header section. The data is formatted as a Version 3 Signature Packet as specified in + /// RFC 2440: OpenPGP Message Format. If this tag is present, then the SIGTAG_GPG tag shall also be present. + /// + RPMSIGTAG_DSA = 267, + + /// + /// The tag contains the RSA signature of the Header section.The data is formatted as a Version 3 Signature Packet as specified in + /// RFC 2440: OpenPGP Message Format. If this tag is present, then the SIGTAG_PGP shall also be present. + /// + RPMSIGTAG_RSA = 268, + + /// + /// This tag specifies the RSA signature of the combined Header and Payload sections. The data is formatted as a Version 3 Signature Packet + /// as specified in RFC 2440: OpenPGP Message Format. + /// + RPMSIGTAG_PGP = 1002, + + /// + /// The tag contains the DSA signature of the combined Header and Payload sections. The data is formatted as a Version 3 Signature Packet + /// as specified in RFC 2440: OpenPGP Message Format. + /// + RPMSIGTAG_GPG = 1005, + } +} diff --git a/DebUOS/Packaging.Targets/RpmTask.cs b/DebUOS/Packaging.Targets/RpmTask.cs new file mode 100644 index 0000000..347bd7c --- /dev/null +++ b/DebUOS/Packaging.Targets/RpmTask.cs @@ -0,0 +1,353 @@ +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Packaging.Targets.IO; +using Packaging.Targets.Rpm; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Packaging.Targets +{ + public class RpmTask : Task + { + [Required] + public string PublishDir + { + get; + set; + } + + [Required] + public string RpmPath + { + get; + set; + } + + [Required] + public string CpioPath + { + get; + set; + } + + [Required] + public string Prefix + { + get; + set; + } + + [Required] + public string Version + { + get; + set; + } + + [Required] + public string Release + { + get; + set; + } + + [Required] + public string PackageName + { + get; + set; + } + + [Required] + public ITaskItem[] Content + { + 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 (x86_64, i686,...). When not set, + /// the target architecture will be derived based on the + /// + public string RpmPackageArchitecture { 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 RPM packages on which the version of .NET Core + /// embedded in this RPM package dpeends. + /// + public ITaskItem[] RpmDotNetDependencies + { + get; + set; + } + + /// + /// Gets or sets a list of RPM packages on which this RPM + /// package dpeends. + /// + public ITaskItem[] RpmDependencies + { + 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 the package vendor. + /// + public string Vendor + { + get; + set; + } + + /// + /// Gets or sets the package description. + /// + public string Description + { + get; + set; + } + + /// + /// Gets or sets the package URL. + /// + public string Url + { + 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) + { + // Some architectures are listed here: + // - https://wiki.centos.org/Download + // - https://docs.fedoraproject.org/ro/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ch01s03.html + RuntimeIdentifiers.ParseRuntimeId(runtimeIdentifier, out _, out _, out Architecture? architecture, out _); + + if (architecture != null) + { + switch (architecture.Value) + { + case Architecture.Arm: + return "armhfp"; + + case Architecture.Arm64: + return "aarch64"; + + case Architecture.X64: + return "x86_64"; + + case Architecture.X86: + return "i386"; + } + } + + return "noarch"; + } + + public override bool Execute() + { + this.Log.LogMessage(MessageImportance.Normal, "Creating RPM package '{0}' from folder '{1}'", this.RpmPath, this.PublishDir); + + var krgen = PgpSigner.GenerateKeyRingGenerator("dotnet", "dotnet"); + var secretKeyRing = krgen.GenerateSecretKeyRing(); + var privateKey = secretKeyRing.GetSecretKey().ExtractPrivateKey("dotnet".ToCharArray()); + var publicKey = secretKeyRing.GetPublicKey(); + + using (var targetStream = File.Open(this.RpmPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) + using (var cpioStream = File.Open(this.CpioPath, 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)); + + archiveEntries = archiveEntries + .OrderBy(e => e.TargetPathWithFinalSlash, StringComparer.Ordinal) + .ToList(); + + CpioFileCreator cpioCreator = new CpioFileCreator(); + cpioCreator.FromArchiveEntries( + archiveEntries, + cpioStream); + cpioStream.Position = 0; + + // Prepare the list of dependencies + List dependencies = new List(); + + if (this.RpmDotNetDependencies != null) + { + dependencies.AddRange( + this.RpmDotNetDependencies.Select( + d => GetPackageDependency(d))); + } + + if (this.RpmDependencies != null) + { + dependencies.AddRange( + this.RpmDependencies.Select( + d => GetPackageDependency(d))); + } + + RpmPackageCreator rpmCreator = new RpmPackageCreator(); + rpmCreator.CreatePackage( + archiveEntries, + cpioStream, + this.PackageName, + this.Version, + !string.IsNullOrEmpty(this.RpmPackageArchitecture) ? this.RpmPackageArchitecture : GetPackageArchitecture(this.RuntimeIdentifier), + this.Release, + this.CreateUser, + this.UserName, + this.InstallService, + this.ServiceName, + this.Vendor, + this.Description, + this.Url, + this.Prefix, + this.PreInstallScript, + this.PostInstallScript, + this.PreRemoveScript, + this.PostRemoveScript, + dependencies, + null, + privateKey, + targetStream); + } + + this.Log.LogMessage(MessageImportance.Normal, "Created RPM package '{0}' from folder '{1}'", this.RpmPath, this.PublishDir); + return true; + } + + private static PackageDependency GetPackageDependency(ITaskItem dependency) + { + if (dependency == null) + { + return null; + } + + return new PackageDependency( + dependency.ItemSpec, + RpmSense.RPMSENSE_EQUAL | RpmSense.RPMSENSE_GREATER, + dependency.GetVersion()); + } + } +} diff --git a/DebUOS/Packaging.Targets/RuntimeIdentifiers.cs b/DebUOS/Packaging.Targets/RuntimeIdentifiers.cs new file mode 100644 index 0000000..ff64a77 --- /dev/null +++ b/DebUOS/Packaging.Targets/RuntimeIdentifiers.cs @@ -0,0 +1,125 @@ +using System; +using System.Runtime.InteropServices; + +namespace Packaging.Targets +{ + /// + /// Provides methods for working with .NET Runtime Identifiers. + /// + public static class RuntimeIdentifiers + { + /// + /// Parses a runtime identifier. + /// + /// + /// The runtime identifier to parse. + /// + /// + /// The operating system name embedded in the runtime identifier. + /// + /// + /// The operating system version embedded in the runtime identifier. + /// + /// + /// The processor architecture embedded in the runtime identifier. + /// + /// + /// Any additional qualifiers (such as aot) embedded in the runtime identifier. + /// + public static void ParseRuntimeId(string runtimeId, out string osName, out string version, out Architecture? architecture, out string qualifiers) + { + osName = null; + version = null; + architecture = null; + qualifiers = null; + + if (string.IsNullOrEmpty(runtimeId)) + { + return; + } + + // We use the following convention in all newly-defined RIDs. Some RIDs (win7-x64, win8-x64) predate this convention and don't follow it, but all new RIDs should follow it. + // [os name].[version]-[architecture]-[additional qualifiers] + // See https://github.com/dotnet/corefx/blob/master/pkg/Microsoft.NETCore.Platforms/readme.md#naming-convention + int versionSeparator = runtimeId.IndexOf('.'); + if (versionSeparator >= 0) + { + osName = runtimeId.Substring(0, versionSeparator); + } + else + { + osName = null; + } + + // As a special case, for "linux-musl", we consider "-musl" to be part of the osName + int muslSeparator = runtimeId.IndexOf("-musl", versionSeparator + 1); + int architectureSeparator = runtimeId.IndexOf('-', muslSeparator + 1); + if (architectureSeparator >= 0) + { + if (versionSeparator >= 0) + { + version = runtimeId.Substring(versionSeparator + 1, architectureSeparator - versionSeparator - 1); + } + else + { + osName = runtimeId.Substring(0, architectureSeparator); + version = null; + } + + qualifiers = runtimeId.Substring(architectureSeparator + 1); + } + else + { + if (versionSeparator >= 0) + { + version = runtimeId.Substring(versionSeparator + 1); + } + else + { + osName = runtimeId; + version = null; + } + + qualifiers = null; + } + + // As a special case, os names like win7, win81 and win10 are processed separately + if (osName.StartsWith("win") && osName.Length > 3) + { + version = osName.Substring(3); + osName = "win"; + } + + architecture = null; + + if (!string.IsNullOrEmpty(qualifiers)) + { + string architectureString = qualifiers; + qualifiers = null; + + architectureSeparator = architectureString.IndexOf('-'); + if (architectureSeparator > 0) + { + qualifiers = architectureString.Substring(architectureSeparator + 1); + architectureString = architectureString.Substring(0, architectureSeparator); + } + + // As a special case, "armel" is mapped to "arm" for now + if (architectureString == "armel") + { + architectureString = "arm"; + } + + if (Enum.TryParse(architectureString, ignoreCase: true, out Architecture parsedArchitecture)) + { + architecture = parsedArchitecture; + } + else + { + // E.g. in the case of "win-aot" + qualifiers = architectureString; + } + } + } + } +} diff --git a/DebUOS/Packaging.Targets/StreamExtensions.cs b/DebUOS/Packaging.Targets/StreamExtensions.cs new file mode 100644 index 0000000..5fa2e4d --- /dev/null +++ b/DebUOS/Packaging.Targets/StreamExtensions.cs @@ -0,0 +1,189 @@ +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Packaging.Targets +{ + /// + /// Provides extension methods for the class. + /// + internal static class StreamExtensions + { + /// + /// Reads a struct from the stream. + /// + /// + /// The type of the struct to read. + /// + /// + /// The to read the struct from. + /// + /// + /// A new struct, with the data read + /// from the stream. + /// + public static T ReadStruct(this Stream stream) + where T : struct + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + var size = Marshal.SizeOf(); + + var data = new byte[size]; + var totalRead = 0; + + while (totalRead < size) + { + var read = stream.Read(data, totalRead, size); + + if (read == 0) + { + break; + } + + totalRead += read; + } + + if (totalRead < size) + { + throw new InvalidOperationException("Not enough data"); + } + + // Convert from network byte order (big endian) to little endian. + RespectEndianness(data); + + var pinnedData = GCHandle.Alloc(data, GCHandleType.Pinned); + + try + { + var ptr = pinnedData.AddrOfPinnedObject(); + return Marshal.PtrToStructure(ptr); + } + finally + { + pinnedData.Free(); + } + } + + /// + /// Writes a struct to a stream. + /// + /// + /// The type of the struct to write. + /// + /// + /// The stream to which to write the struct. + /// + /// + /// The struct to write to the stram. + /// + public static int WriteStruct(this Stream stream, T data) + where T : struct + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + byte[] bytes = new byte[Marshal.SizeOf()]; + + GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + + try + { + Marshal.StructureToPtr(data, handle.AddrOfPinnedObject(), true); + } + finally + { + handle.Free(); + } + + RespectEndianness(bytes); + + stream.Write(bytes, 0, bytes.Length); + return bytes.Length; + } + + /// + /// Writes a to a using big-endian encoding. + /// + /// + /// The to which to write the value. + /// + /// + /// The value to write to the stream. + /// + public static void WriteBE(this Stream stream, short value) + { + var data = BitConverter.GetBytes(value); + Array.Reverse(data); + stream.Write(data, 0, data.Length); + } + + /// + /// Writes a to a using big-endian encoding. + /// + /// + /// The to which to write the value. + /// + /// + /// The value to write to the stream. + /// + public static void WriteBE(this Stream stream, int value) + { + var data = BitConverter.GetBytes(value); + Array.Reverse(data); + stream.Write(data, 0, data.Length); + } + + /// + /// Writes a to a using big-endian encoding. + /// + /// + /// The to which to write the value. + /// + /// + /// The value to write to the stream. + /// + public static void WriteBE(this Stream stream, long value) + { + var data = BitConverter.GetBytes(value); + Array.Reverse(data); + stream.Write(data, 0, data.Length); + } + + private static void RespectEndianness(byte[] data) + { + foreach (var field in typeof(T).GetTypeInfo().DeclaredFields) + { + int length = 0; + + var type = field.FieldType; + + if (type.GetTypeInfo().IsEnum) + { + type = Enum.GetUnderlyingType(type); + } + + if (type == typeof(short) || type == typeof(ushort)) + { + length = 2; + } + else if (type == typeof(int) || type == typeof(uint)) + { + length = 4; + } + + if (length > 0) + { + var offset = Marshal.OffsetOf(field.Name).ToInt32(); + Array.Reverse(data, offset, length); + } + } + } + } +} diff --git a/DebUOS/Packaging.Targets/TarballTask.cs b/DebUOS/Packaging.Targets/TarballTask.cs new file mode 100644 index 0000000..3981804 --- /dev/null +++ b/DebUOS/Packaging.Targets/TarballTask.cs @@ -0,0 +1,64 @@ +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Packaging.Targets.IO; +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; + +namespace Packaging.Targets +{ + public class TarballTask : Task + { + [Required] + public string PublishDir + { get; set; } + + [Required] + public string TarballPath + { get; set; } + + [Required] + public ITaskItem[] Content + { get; set; } + + public string Prefix + { get; set; } + + public override bool Execute() + { + this.Log.LogMessage(MessageImportance.Normal, "Creating tarball '{0}' from folder '{1}'", this.TarballPath, this.PublishDir); + + this.CreateLinuxTarball(); + + this.Log.LogMessage(MessageImportance.Normal, "Created tarball '{0}' from folder '{1}'", this.TarballPath, this.PublishDir); + return true; + } + + private void CreateLinuxTarball() + { + ArchiveBuilder archiveBuilder = new ArchiveBuilder() + { + Log = this.Log, + }; + + var archiveEntries = archiveBuilder.FromDirectory( + this.PublishDir, + null, + this.Prefix, + this.Content); + + DebTask.EnsureDirectories(archiveEntries, includeRoot: false); + + archiveEntries = archiveEntries + .OrderBy(e => e.TargetPathWithFinalSlash, StringComparer.Ordinal) + .ToList(); + + using (var stream = File.Create(this.TarballPath)) + using (var gzipStream = new GZipStream(stream, CompressionMode.Compress)) + { + TarFileCreator.FromArchiveEntries(archiveEntries, gzipStream); + } + } + } +} diff --git a/DebUOS/Packaging.Targets/TaskItemExtensions.cs b/DebUOS/Packaging.Targets/TaskItemExtensions.cs new file mode 100644 index 0000000..5b094ae --- /dev/null +++ b/DebUOS/Packaging.Targets/TaskItemExtensions.cs @@ -0,0 +1,185 @@ +using Microsoft.Build.Framework; +using System; +using System.IO; +using System.Linq; + +namespace Packaging.Targets +{ + /// + /// Provides extension methods for the interface. + /// + public static class TaskItemExtensions + { + /// + /// Gets a value indicating whether this item is copied to the publish directory or not. + /// + /// + /// The item for which to determine whether it is copied or not. + /// + /// + /// if the file is copied over; otherwise, . + /// + public static bool IsPublished(this ITaskItem item) + { + if (item == null) + { + return false; + } + + if (!item.MetadataNames.OfType().Contains("CopyToPublishDirectory")) + { + return false; + } + + var copyToPublishDirectoryValue = item.GetMetadata("CopyToPublishDirectory"); + CopyToDirectoryValue copyToPublishDirectory; + + if (!Enum.TryParse(copyToPublishDirectoryValue, out copyToPublishDirectory)) + { + return false; + } + + return copyToPublishDirectory != CopyToDirectoryValue.DoNotCopy; + } + + /// + /// Gets the path to where the file is published. + /// + /// + /// The item for which to determine the publish path. + /// + /// + /// The path to where the file is published. + /// + public static string GetPublishedPath(this ITaskItem item) + { + if (item == null) + { + return null; + } + + var link = item.GetMetadata("Link"); + if (!string.IsNullOrEmpty(link)) + { + return link.Replace("\\", "/"); + } + + var relativeDirectory = item.GetMetadata("RelativeDir"); + var filename = item.GetMetadata("FileName"); + var extension = item.GetMetadata("Extension"); + + return Path.Combine(relativeDirectory, $"{filename}{extension}").Replace("\\", "/"); + } + + /// + /// Gets the path of the file in the Linux filesystem. + /// + /// + /// The item for which to get the Linux path. + /// + /// + /// The path to the file on the Linux filesystem. + /// + public static string GetLinuxPath(this ITaskItem item) + { + return TryGetValue(item, "LinuxPath"); + } + + /// + /// Gets the file mode of the file in the Linux filesystem. + /// + /// + /// The item for which to get the file mode. + /// + /// + /// The file mode of the file on the Linux file system. + /// + public static string GetLinuxFileMode(this ITaskItem item) + { + return TryGetValue(item, "LinuxFileMode"); + } + + /// + /// Gets the Linux owner of the file. + /// + /// + /// The item for which to get the file owner. + /// + /// + /// The owner of the file. + /// + public static string GetOwner(this ITaskItem item) + { + return TryGetValue(item, "Owner", "root"); + } + + /// + /// Gets the Linux group of the file. + /// + /// + /// The item for which to get the file group. + /// + /// + /// The group of the file. + /// + public static string GetGroup(this ITaskItem item) + { + return TryGetValue(item, "Group", "root"); + } + + /// + /// Gets the version of the RPM dependency. + /// + /// + /// The task item which represents the RPM dependency. + /// + /// + /// The version of the dependency. + /// + public static string GetVersion(this ITaskItem item) + { + return TryGetValue(item, "Version", null); + } + + /// + /// Gets a value indicating whether the item should be removed when the + /// program is removed. + /// + /// + /// The item to inspect. + /// + /// + /// if the file should be removed; otherwise, + /// . + /// + public static bool GetRemoveOnUninstall(this ITaskItem item) + { + var valueString = TryGetValue(item, "RemoveOnUninstall", "false"); + bool value; + + if (!bool.TryParse(valueString, out value)) + { + return false; + } + + return value; + } + + private static string TryGetValue(ITaskItem item, string name, string @default = null) + { + if (item == null) + { + return @default; + } + + if (!item.MetadataNames.OfType().Contains(name)) + { + return @default; + } + + var linuxPath = item.GetMetadata(name); + + return linuxPath; + } + } +} diff --git a/DebUOS/Packaging.Targets/ZipTask.cs b/DebUOS/Packaging.Targets/ZipTask.cs new file mode 100644 index 0000000..d6369c2 --- /dev/null +++ b/DebUOS/Packaging.Targets/ZipTask.cs @@ -0,0 +1,68 @@ +using ICSharpCode.SharpZipLib.Zip; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Packaging.Targets +{ + public class ZipTask : Task + { + [Required] + public string PublishDir + { + get; + set; + } + + [Required] + public string ZipPath + { + get; + set; + } + + public override bool Execute() + { + this.Log.LogMessage(MessageImportance.Normal, "Creating zip archive '{0}' from folder '{1}'", this.ZipPath, this.PublishDir); + + this.CreateWindowsTarball(); + + this.Log.LogMessage(MessageImportance.Normal, "Created zip archive '{0}' from folder '{1}'", this.ZipPath, this.PublishDir); + return true; + } + + private void CreateWindowsTarball() + { + using (var stream = File.Create(this.ZipPath)) + using (var zipFile = ZipFile.Create(stream)) + { + this.AddDirectory(zipFile, this.PublishDir, string.Empty); + } + } + + private void AddDirectory(ZipFile zipFile, string directory, string directoryEntryName) + { + if (directoryEntryName != string.Empty) + { + zipFile.BeginUpdate(); + zipFile.AddDirectory(directoryEntryName); + zipFile.CommitUpdate(); + } + + foreach (var file in Directory.GetFiles(directory)) + { + zipFile.BeginUpdate(); + zipFile.Add(file, Path.Combine(directoryEntryName, Path.GetFileName(file))); + zipFile.CommitUpdate(); + } + + foreach (var child in Directory.GetDirectories(directory)) + { + this.AddDirectory(zipFile, child, Path.Combine(directoryEntryName, Path.GetFileName(child))); + } + } + } +} diff --git a/DebUOS/Packaging.Targets/build/Packaging.Targets.targets b/DebUOS/Packaging.Targets/build/Packaging.Targets.targets new file mode 100644 index 0000000..c03a7ce --- /dev/null +++ b/DebUOS/Packaging.Targets/build/Packaging.Targets.targets @@ -0,0 +1,285 @@ + + + + + + + + + + $(Version) + 1.0.0 + + $(TargetName) + $(PackagePrefix).$(PackageVersion).$(RuntimeIdentifier) + $(PackagePrefix).$(PackageVersion) + $(IntermediateOutputPath)$(PackageName) + $(TargetDir) + $([MSBuild]::EnsureTrailingSlash($(PackageDir)))$(PackageName) + false + false + $(Authors) + Anonymous <noreply@example.com> + $(Description) + $(PackageName) version $(PackageVersion) - $(Release) + true + $(AssemblyName)$(_NativeExecutableExtension) + + + + + + $(PackagePath).rpm + $(IntermediatePackagePath).cpio + /usr/share/$(PackagePrefix) + 0 + $(PackagePrefix) + $(PackagePrefix) + $(PackagePrefix) + $(Authors) + $(PackageDescription) + $(PackageProjectUrl) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(PackagePath).deb + $(IntermediatePackagePath).deb.tar + $(IntermediatePackagePath).deb.tar.xz + /usr/share/$(PackagePrefix) + $(PackagePrefix) + $(PackagePrefix) + $(PackagePrefix) + misc + extra + $(PackageProjectUrl) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(PackagePath).tar.gz + . + + + + + + + + + + + + $(PackagePath).zip + + + + + + + + + + + + $(TargetName) + $(TargetName) + $(TargetName)Feature + $(TargetName) + $(TargetName) + 1.0.0.0 + 1033 + + + + -nologo -dPublishDir="$(PublishDir)\" -dSetupProductName="$(SetupProductName)" -dSetupProductManufacturer="$(SetupProductManufacturer)" -dSetupFeatureId="$(SetupFeatureId)" -dSetupFeatureName="$(SetupFeatureName)" -dSetupInstallFolderName="$(SetupInstallFolderName)" -dSetupProductVersion="$(SetupProductVersion)" -dSetupProductLanguage="$(SetupProductLanguage)" + + + + C:\Program Files (x86)\WiX Toolset v3.10\ + $(IntermediateOutputPath)$(TargetName).harvest.wxs + $(IntermediateOutputPath)$(TargetName).harvest.wixobj + $(MSBuildThisFileDirectory)\Product.wxs + $(IntermediateOutputPath)$(TargetName).product.wixobj + $(TargetDir)$(TargetName).msi + $(WixInstallPath)bin\heat.exe + $(WixInstallPath)bin\candle.exe + $(WixInstallPath)bin\light.exe + + + + + + + + + + + + diff --git a/DebUOS/Packaging.Targets/build/Product.wxs b/DebUOS/Packaging.Targets/build/Product.wxs new file mode 100644 index 0000000..70c0721 --- /dev/null +++ b/DebUOS/Packaging.Targets/build/Product.wxs @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/dotnet-packaging.ruleset b/DebUOS/Packaging.Targets/dotnet-packaging.ruleset new file mode 100644 index 0000000..128cfc1 --- /dev/null +++ b/DebUOS/Packaging.Targets/dotnet-packaging.ruleset @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/runtimes/win7-x64/native/lzma.dll b/DebUOS/Packaging.Targets/runtimes/win7-x64/native/lzma.dll new file mode 100644 index 0000000000000000000000000000000000000000..2a2e4c71b4f4da7c206a956f495a27d45f59e50d GIT binary patch literal 150528 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 literal 0 HcmV?d00001 diff --git a/DebUOS/Packaging.Targets/stylecop.json b/DebUOS/Packaging.Targets/stylecop.json new file mode 100644 index 0000000..125bda9 --- /dev/null +++ b/DebUOS/Packaging.Targets/stylecop.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "Quamotion" + } + } +} From abda1485f1fb3de0a4087085706c8b952e2f4b7b Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 27 Dec 2023 11:37:36 +0800 Subject: [PATCH 03/74] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E6=84=9F=E8=B0=A2?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DotNETBuild.sln | 15 +++++++++++++++ README.md | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/DotNETBuild.sln b/DotNETBuild.sln index 3802335..a977a60 100644 --- a/DotNETBuild.sln +++ b/DotNETBuild.sln @@ -93,6 +93,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DebUOS", "DebUOS", "{AC9904 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Packing.DebUOS", "DebUOS\Packing.DebUOS\Packing.DebUOS.csproj", "{209825D6-7821-4E0E-B3C8-E3504FF65B36}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Packaging.Targets", "DebUOS\Packaging.Targets\Packaging.Targets.csproj", "{C69F9A99-8110-4379-A22C-176B3E05E481}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -487,6 +489,18 @@ Global {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|x64.Build.0 = Release|Any CPU {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|x86.ActiveCfg = Release|Any CPU {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|x86.Build.0 = Release|Any CPU + {C69F9A99-8110-4379-A22C-176B3E05E481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C69F9A99-8110-4379-A22C-176B3E05E481}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C69F9A99-8110-4379-A22C-176B3E05E481}.Debug|x64.ActiveCfg = Debug|Any CPU + {C69F9A99-8110-4379-A22C-176B3E05E481}.Debug|x64.Build.0 = Debug|Any CPU + {C69F9A99-8110-4379-A22C-176B3E05E481}.Debug|x86.ActiveCfg = Debug|Any CPU + {C69F9A99-8110-4379-A22C-176B3E05E481}.Debug|x86.Build.0 = Debug|Any CPU + {C69F9A99-8110-4379-A22C-176B3E05E481}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C69F9A99-8110-4379-A22C-176B3E05E481}.Release|Any CPU.Build.0 = Release|Any CPU + {C69F9A99-8110-4379-A22C-176B3E05E481}.Release|x64.ActiveCfg = Release|Any CPU + {C69F9A99-8110-4379-A22C-176B3E05E481}.Release|x64.Build.0 = Release|Any CPU + {C69F9A99-8110-4379-A22C-176B3E05E481}.Release|x86.ActiveCfg = Release|Any CPU + {C69F9A99-8110-4379-A22C-176B3E05E481}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -508,6 +522,7 @@ Global {93037B85-0A27-4A6E-B485-2F4544ABEFDF} = {2C43AF3C-C3B2-4B8A-81E2-DA6D8A53356A} {4360E6F1-680C-45D7-A4E0-1663A237709A} = {3C8DD4B9-55DB-450E-B06A-B7C4EB3A9CF1} {209825D6-7821-4E0E-B3C8-E3504FF65B36} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} + {C69F9A99-8110-4379-A22C-176B3E05E481} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A6E377C6-EDE0-4135-951F-78C62B63DE96} diff --git a/README.md b/README.md index 9e19f2d..8b1ac47 100644 --- a/README.md +++ b/README.md @@ -460,4 +460,5 @@ SyncTool sync [参数] 排名无先后 -- [nmklotas/GitLabApiClient: GitLab API client](https://github.com/nmklotas/GitLabApiClient ) \ No newline at end of file +- [nmklotas/GitLabApiClient: GitLab API client](https://github.com/nmklotas/GitLabApiClient ) +- [https://github.com/quamotion/dotnet-packaging](https://github.com/quamotion/dotnet-packaging) \ No newline at end of file From 51c5a2e624d3cf72a022856fd1a17587834d6029 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 27 Dec 2023 11:43:17 +0800 Subject: [PATCH 04/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.Targets/CopyToDirectoryValue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DebUOS/Packaging.Targets/CopyToDirectoryValue.cs b/DebUOS/Packaging.Targets/CopyToDirectoryValue.cs index 9bd9324..576d7fb 100644 --- a/DebUOS/Packaging.Targets/CopyToDirectoryValue.cs +++ b/DebUOS/Packaging.Targets/CopyToDirectoryValue.cs @@ -11,7 +11,7 @@ internal enum CopyToDirectoryValue DoNotCopy, /// - /// The file is alwasy copied. + /// The file is always copied. /// Always, From 418e6c88438493397e475abd3f9c4b5d1679d2ab Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 27 Dec 2023 11:44:21 +0800 Subject: [PATCH 05/74] =?UTF-8?q?=E5=88=A0=E6=8E=89=E4=B8=8D=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.Targets/ArchiveBuilder.cs | 445 ------------------ DebUOS/Packaging.Targets/DebTask.cs | 363 -------------- .../Packaging.Targets.csproj | 74 +-- DebUOS/Packaging.Targets/RpmTask.cs | 353 -------------- DebUOS/Packaging.Targets/TarballTask.cs | 64 --- .../Packaging.Targets/TaskItemExtensions.cs | 185 -------- DebUOS/Packaging.Targets/ZipTask.cs | 68 --- .../build/Packaging.Targets.targets | 285 ----------- DebUOS/Packaging.Targets/build/Product.wxs | 31 -- .../dotnet-packaging.ruleset | 73 --- DebUOS/Packaging.Targets/stylecop.json | 8 - 11 files changed, 3 insertions(+), 1946 deletions(-) delete mode 100644 DebUOS/Packaging.Targets/ArchiveBuilder.cs delete mode 100644 DebUOS/Packaging.Targets/DebTask.cs delete mode 100644 DebUOS/Packaging.Targets/RpmTask.cs delete mode 100644 DebUOS/Packaging.Targets/TarballTask.cs delete mode 100644 DebUOS/Packaging.Targets/TaskItemExtensions.cs delete mode 100644 DebUOS/Packaging.Targets/ZipTask.cs delete mode 100644 DebUOS/Packaging.Targets/build/Packaging.Targets.targets delete mode 100644 DebUOS/Packaging.Targets/build/Product.wxs delete mode 100644 DebUOS/Packaging.Targets/dotnet-packaging.ruleset delete mode 100644 DebUOS/Packaging.Targets/stylecop.json diff --git a/DebUOS/Packaging.Targets/ArchiveBuilder.cs b/DebUOS/Packaging.Targets/ArchiveBuilder.cs deleted file mode 100644 index 9f038be..0000000 --- a/DebUOS/Packaging.Targets/ArchiveBuilder.cs +++ /dev/null @@ -1,445 +0,0 @@ -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Packaging.Targets.IO; -using Packaging.Targets.Rpm; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; - -namespace Packaging.Targets -{ - /// - /// Creates a list of objects based on the publish directory of a .NET Core application. - /// - internal class ArchiveBuilder - { - private IFileAnalyzer fileAnayzer; - private uint inode = 0; - - /// - /// Initializes a new instance of the class. - /// - public ArchiveBuilder() - { - this.fileAnayzer = new FileAnalyzer(); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A which can extract item metadata. - /// - public ArchiveBuilder(IFileAnalyzer analyzer) - { - if (analyzer == null) - { - throw new ArgumentNullException(nameof(analyzer)); - } - - this.fileAnayzer = analyzer; - } - - /// - /// Gets or sets a to which status messages can be written. - /// - public TaskLoggingHelper Log - { - get; - set; - } - - /// - /// Extracts the objects from a CPIO file. - /// - /// - /// The CPIO file from which to extract the entries. - /// - /// - /// A list of objects representing the data in the CPIO file. - /// - public List FromCpio(CpioFile file) - { - List value = new List(); - byte[] buffer = new byte[1024]; - byte[] fileHeader = null; - - while (file.Read()) - { - fileHeader = null; - - ArchiveEntry entry = new ArchiveEntry() - { - FileSize = file.EntryHeader.FileSize, - Group = "root", - Owner = "root", - Inode = file.EntryHeader.Ino, - Mode = file.EntryHeader.FileMode, - Modified = file.EntryHeader.LastModified, - TargetPath = file.FileName, - Type = ArchiveEntryType.None, - LinkTo = string.Empty, - Sha256 = Array.Empty(), - SourceFilename = null, - IsAscii = true - }; - - if (entry.Mode.HasFlag(LinuxFileMode.S_IFREG) && !entry.Mode.HasFlag(LinuxFileMode.S_IFLNK)) - { - using (var fileStream = file.Open()) - using (var hasher = IncrementalHash.CreateHash(HashAlgorithmName.SHA256)) - { - int read; - - while (true) - { - read = fileStream.Read(buffer, 0, buffer.Length); - - if (fileHeader == null) - { - fileHeader = new byte[read]; - Buffer.BlockCopy(buffer, 0, fileHeader, 0, read); - } - - hasher.AppendData(buffer, 0, read); - entry.IsAscii = entry.IsAscii && fileHeader.All(c => c < 128); - - if (read < buffer.Length) - { - break; - } - } - - entry.Sha256 = hasher.GetHashAndReset(); - } - - entry.Type = this.GetArchiveEntryType(fileHeader); - } - else if (entry.Mode.HasFlag(LinuxFileMode.S_IFLNK)) - { - using (var fileStream = file.Open()) - using (var reader = new StreamReader(fileStream, Encoding.UTF8)) - { - entry.LinkTo = reader.ReadToEnd(); - } - } - else - { - file.Skip(); - } - - if (entry.Mode.HasFlag(LinuxFileMode.S_IFDIR)) - { - entry.FileSize = 0x1000; - } - - if (entry.TargetPath.StartsWith(".")) - { - entry.TargetPath = entry.TargetPath.Substring(1); - } - - value.Add(entry); - } - - return value; - } - - public Collection FromLinuxFolders(ITaskItem[] metadata) - { - Collection value = new Collection(); - - // This can be null if the user did not define any folders. - // In that case: nothing to do. - if (metadata != null) - { - foreach (var folder in metadata) - { - var path = folder.ItemSpec.Replace("\\", "/"); - - // Default file mode - LinuxFileMode 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; - mode = this.GetFileMode(path, folder, mode); - - // Write out an entry for the directory - ArchiveEntry directoryEntry = new ArchiveEntry() - { - FileSize = 0x00001000, - Sha256 = Array.Empty(), - Mode = mode, - Modified = DateTime.Now, - Group = folder.GetGroup(), - Owner = folder.GetOwner(), - Inode = this.inode++, - TargetPath = path, - LinkTo = string.Empty, - RemoveOnUninstall = folder.GetRemoveOnUninstall() - }; - - value.Add(directoryEntry); - } - } - - return value; - } - - /// - /// Extracts the objects from a directory. - /// - /// - /// The directory from which to extract the entries. - /// - /// - /// The prefix of the target directory. - /// - /// - /// Additional metadata to use. - /// - /// - /// A list of objects representing the data in the directory. - /// - public List FromDirectory(string directory, string appHost, string prefix, ITaskItem[] metadata) - { - List value = new List(); - 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(), - }); - } - - return value; - } - - protected void AddDirectory(string directory, string relativePath, string prefix, List value, ITaskItem[] metadata) - { - this.inode++; - - // The order in which the files appear in the cpio archive is important; if this is not respected xzdio - // will report errors like: - // error: unpacking of archive failed on file ./usr/share/quamotion/mscorlib.dll: cpio: Archive file not in header - var entries = Directory.GetFileSystemEntries(directory).OrderBy(e => Directory.Exists(e) ? e + "/" : e, StringComparer.Ordinal).ToArray(); - - foreach (var entry in entries) - { - if (File.Exists(entry)) - { - this.AddFile(entry, relativePath + Path.GetFileName(entry), prefix, value, metadata); - } - else - { - this.AddDirectory(entry, relativePath + Path.GetFileName(entry) + "/", prefix + "/" + Path.GetFileName(entry), value, metadata); - } - } - } - - protected void AddFile(string entry, string relativePath, string prefix, List value, ITaskItem[] metadata) - { - var fileName = Path.GetFileName(entry); - - byte[] fileHeader = null; - byte[] hash = null; - byte[] md5hash = null; - byte[] buffer = new byte[1024]; - bool isAscii = true; - - var fileMetadata = metadata.SingleOrDefault(m => m.IsPublished() && string.Equals(relativePath, m.GetPublishedPath())); - - using (Stream fileStream = File.OpenRead(entry)) - { - // Skip hidden and empty files - this would case rpmlint errors. - if (fileName.StartsWith(".")) - { - this.Log.LogWarning($"Ignoring file {relativePath} because it starts with the '.' character and is considered a hidden file."); - return; - } - - if (fileStream.Length == 0) - { - this.Log.LogWarning($"Ignoring file {relativePath} because it is empty."); - return; - } - - using (var hasher = IncrementalHash.CreateHash(HashAlgorithmName.SHA256)) - using (var md5hasher = IncrementalHash.CreateHash(HashAlgorithmName.MD5)) - { - int read; - - while (true) - { - read = fileStream.Read(buffer, 0, buffer.Length); - - if (fileHeader == null) - { - fileHeader = new byte[read]; - Buffer.BlockCopy(buffer, 0, fileHeader, 0, read); - } - - hasher.AppendData(buffer, 0, read); - md5hasher.AppendData(buffer, 0, read); - isAscii = isAscii && buffer.All(c => c < 128); - - if (read < buffer.Length) - { - break; - } - } - - hash = hasher.GetHashAndReset(); - md5hash = md5hasher.GetHashAndReset(); - } - - // Only support ELF32 and ELF64 colors; otherwise default to BLACK. - ArchiveEntryType entryType = this.GetArchiveEntryType(fileHeader); - - var mode = LinuxFileMode.S_IROTH | LinuxFileMode.S_IRGRP | LinuxFileMode.S_IRUSR | LinuxFileMode.S_IFREG; - - if (entryType == ArchiveEntryType.Executable32 || entryType == ArchiveEntryType.Executable64) - { - mode |= LinuxFileMode.S_IXOTH | LinuxFileMode.S_IXGRP | LinuxFileMode.S_IWUSR | LinuxFileMode.S_IXUSR; - } - - // If a Linux path has been specified, use that one, else, use the default one based on the prefix - // + current file name. - string name = fileMetadata?.GetLinuxPath(); - - if (name == null) - { - if (!string.IsNullOrEmpty(prefix)) - { - name = prefix + "/" + fileName; - } - else - { - name = fileName; - } - } - - string linkTo = string.Empty; - - if (mode.HasFlag(LinuxFileMode.S_IFLNK)) - { - // Find the link text - int stringEnd = 0; - - while (stringEnd < fileHeader.Length - 1 && fileHeader[stringEnd] != 0) - { - stringEnd++; - } - - linkTo = Encoding.UTF8.GetString(fileHeader, 0, stringEnd + 1); - hash = new byte[] { }; - } - - mode = this.GetFileMode(name, fileMetadata, mode); - - ArchiveEntry archiveEntry = new ArchiveEntry() - { - FileSize = (uint)fileStream.Length, - Group = fileMetadata.GetGroup(), - Owner = fileMetadata.GetOwner(), - Modified = File.GetLastWriteTimeUtc(entry), - SourceFilename = entry, - TargetPath = name, - Sha256 = hash, - Md5Hash = md5hash, - Type = entryType, - LinkTo = linkTo, - Inode = this.inode++, - IsAscii = isAscii, - Mode = mode - }; - - value.Add(archiveEntry); - } - } - - private ArchiveEntryType GetArchiveEntryType(byte[] fileHeader) - { - if (ElfFile.IsElfFile(fileHeader)) - { - ElfHeader elfHeader = ElfFile.ReadHeader(fileHeader); - - if (elfHeader.@class == ElfClass.Elf32) - { - return ArchiveEntryType.Executable32; - } - else - { - return ArchiveEntryType.Executable64; - } - } - - return ArchiveEntryType.None; - } - - /// - /// Gets the file mode for a file or directory entry, based on a default value - /// and the value of the LinuxFileMode attribute, if set. - /// - /// - /// The name (path) of the entry. Only used in error messages. - /// - /// - /// The metadata for the current entry. - /// - /// - /// The default mode. This mode is used to determine the file type (directory/file), - /// and when no LinuxFileMode value is specified. - /// - /// - /// The for the current entry. - /// - private LinuxFileMode GetFileMode(string name, ITaskItem metadata, LinuxFileMode defaultMode) - { - LinuxFileMode mode = defaultMode; - LinuxFileMode defaultFileTypeMask = defaultMode & LinuxFileMode.FileTypeMask; - - // If the user has chosen to override the file node, respect that - var overridenFileMode = metadata?.GetLinuxFileMode(); - - if (overridenFileMode != null) - { - // We expect the user to specify the file mode in its octal representation, - // such as 755. We don't expect users to specify the higher bits (e.g. - // S_IFREG). - try - { - mode = (LinuxFileMode)Convert.ToUInt32(overridenFileMode, 8); - - LinuxFileMode fileType = mode & LinuxFileMode.FileTypeMask; - - if (fileType != LinuxFileMode.None && fileType != defaultFileTypeMask) - { - this.Log.LogWarning($"An invalid file type of '{fileType}' has been set for file '{name}'. The file type will be reset to {defaultFileTypeMask}."); - } - - // Override the file type mask and hardcode it to the file type mask from the default mode. - // In practice this will ensure the S_IFREG or S_IFDIR flag is set. - mode = (mode & ~LinuxFileMode.FileTypeMask) | defaultFileTypeMask; - } - catch (Exception) - { - throw new Exception($"Could not parse the file mode '{overridenFileMode}' for file '{name}'. Make sure to set the LinuxFileMode attriubute to an octal representation of a Unix file mode."); - } - } - - return mode; - } - } -} diff --git a/DebUOS/Packaging.Targets/DebTask.cs b/DebUOS/Packaging.Targets/DebTask.cs deleted file mode 100644 index 586873e..0000000 --- a/DebUOS/Packaging.Targets/DebTask.cs +++ /dev/null @@ -1,363 +0,0 @@ -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); - } - } -} \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/Packaging.Targets.csproj b/DebUOS/Packaging.Targets/Packaging.Targets.csproj index 91d5817..7a5892c 100644 --- a/DebUOS/Packaging.Targets/Packaging.Targets.csproj +++ b/DebUOS/Packaging.Targets/Packaging.Targets.csproj @@ -3,91 +3,23 @@ netstandard2.0 This package supports the dotnet-pack and dotnet-zip packages. Once you've installed this package together with dotnet-zip or dotnet-tarball, you can run commands such as dotnet zip or dotnet tarball to generate .zip or .tar.gz archives which contain the published output of your project. True + + false - - AnyCPU - - - - tools - - true - true - snupkg - true - MIT - true - + - - - all - - - all - - all - all - - - all - - - true - build\ - - - - true - tools\netstandard2.0\ - - - - - true - tools\netstandard2.0\ - - - - true - runtimes\win7-x64\native\ - PreserveNewest - - - - dotnet-packaging.ruleset - - - - - - - - <!-- This file is auto-generated. Do not edit manually --> - <Project> - <PropertyGroup> - <PackagingNuGetVersion>$(NuGetPackageVersion)</PackagingNuGetVersion> - </PropertyGroup> - </Project> - - $(NuGetPackageVersion) - - - - - diff --git a/DebUOS/Packaging.Targets/RpmTask.cs b/DebUOS/Packaging.Targets/RpmTask.cs deleted file mode 100644 index 347bd7c..0000000 --- a/DebUOS/Packaging.Targets/RpmTask.cs +++ /dev/null @@ -1,353 +0,0 @@ -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Packaging.Targets.IO; -using Packaging.Targets.Rpm; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; - -namespace Packaging.Targets -{ - public class RpmTask : Task - { - [Required] - public string PublishDir - { - get; - set; - } - - [Required] - public string RpmPath - { - get; - set; - } - - [Required] - public string CpioPath - { - get; - set; - } - - [Required] - public string Prefix - { - get; - set; - } - - [Required] - public string Version - { - get; - set; - } - - [Required] - public string Release - { - get; - set; - } - - [Required] - public string PackageName - { - get; - set; - } - - [Required] - public ITaskItem[] Content - { - 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 (x86_64, i686,...). When not set, - /// the target architecture will be derived based on the - /// - public string RpmPackageArchitecture { 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 RPM packages on which the version of .NET Core - /// embedded in this RPM package dpeends. - /// - public ITaskItem[] RpmDotNetDependencies - { - get; - set; - } - - /// - /// Gets or sets a list of RPM packages on which this RPM - /// package dpeends. - /// - public ITaskItem[] RpmDependencies - { - 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 the package vendor. - /// - public string Vendor - { - get; - set; - } - - /// - /// Gets or sets the package description. - /// - public string Description - { - get; - set; - } - - /// - /// Gets or sets the package URL. - /// - public string Url - { - 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) - { - // Some architectures are listed here: - // - https://wiki.centos.org/Download - // - https://docs.fedoraproject.org/ro/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ch01s03.html - RuntimeIdentifiers.ParseRuntimeId(runtimeIdentifier, out _, out _, out Architecture? architecture, out _); - - if (architecture != null) - { - switch (architecture.Value) - { - case Architecture.Arm: - return "armhfp"; - - case Architecture.Arm64: - return "aarch64"; - - case Architecture.X64: - return "x86_64"; - - case Architecture.X86: - return "i386"; - } - } - - return "noarch"; - } - - public override bool Execute() - { - this.Log.LogMessage(MessageImportance.Normal, "Creating RPM package '{0}' from folder '{1}'", this.RpmPath, this.PublishDir); - - var krgen = PgpSigner.GenerateKeyRingGenerator("dotnet", "dotnet"); - var secretKeyRing = krgen.GenerateSecretKeyRing(); - var privateKey = secretKeyRing.GetSecretKey().ExtractPrivateKey("dotnet".ToCharArray()); - var publicKey = secretKeyRing.GetPublicKey(); - - using (var targetStream = File.Open(this.RpmPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) - using (var cpioStream = File.Open(this.CpioPath, 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)); - - archiveEntries = archiveEntries - .OrderBy(e => e.TargetPathWithFinalSlash, StringComparer.Ordinal) - .ToList(); - - CpioFileCreator cpioCreator = new CpioFileCreator(); - cpioCreator.FromArchiveEntries( - archiveEntries, - cpioStream); - cpioStream.Position = 0; - - // Prepare the list of dependencies - List dependencies = new List(); - - if (this.RpmDotNetDependencies != null) - { - dependencies.AddRange( - this.RpmDotNetDependencies.Select( - d => GetPackageDependency(d))); - } - - if (this.RpmDependencies != null) - { - dependencies.AddRange( - this.RpmDependencies.Select( - d => GetPackageDependency(d))); - } - - RpmPackageCreator rpmCreator = new RpmPackageCreator(); - rpmCreator.CreatePackage( - archiveEntries, - cpioStream, - this.PackageName, - this.Version, - !string.IsNullOrEmpty(this.RpmPackageArchitecture) ? this.RpmPackageArchitecture : GetPackageArchitecture(this.RuntimeIdentifier), - this.Release, - this.CreateUser, - this.UserName, - this.InstallService, - this.ServiceName, - this.Vendor, - this.Description, - this.Url, - this.Prefix, - this.PreInstallScript, - this.PostInstallScript, - this.PreRemoveScript, - this.PostRemoveScript, - dependencies, - null, - privateKey, - targetStream); - } - - this.Log.LogMessage(MessageImportance.Normal, "Created RPM package '{0}' from folder '{1}'", this.RpmPath, this.PublishDir); - return true; - } - - private static PackageDependency GetPackageDependency(ITaskItem dependency) - { - if (dependency == null) - { - return null; - } - - return new PackageDependency( - dependency.ItemSpec, - RpmSense.RPMSENSE_EQUAL | RpmSense.RPMSENSE_GREATER, - dependency.GetVersion()); - } - } -} diff --git a/DebUOS/Packaging.Targets/TarballTask.cs b/DebUOS/Packaging.Targets/TarballTask.cs deleted file mode 100644 index 3981804..0000000 --- a/DebUOS/Packaging.Targets/TarballTask.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Packaging.Targets.IO; -using System; -using System.IO; -using System.IO.Compression; -using System.Linq; - -namespace Packaging.Targets -{ - public class TarballTask : Task - { - [Required] - public string PublishDir - { get; set; } - - [Required] - public string TarballPath - { get; set; } - - [Required] - public ITaskItem[] Content - { get; set; } - - public string Prefix - { get; set; } - - public override bool Execute() - { - this.Log.LogMessage(MessageImportance.Normal, "Creating tarball '{0}' from folder '{1}'", this.TarballPath, this.PublishDir); - - this.CreateLinuxTarball(); - - this.Log.LogMessage(MessageImportance.Normal, "Created tarball '{0}' from folder '{1}'", this.TarballPath, this.PublishDir); - return true; - } - - private void CreateLinuxTarball() - { - ArchiveBuilder archiveBuilder = new ArchiveBuilder() - { - Log = this.Log, - }; - - var archiveEntries = archiveBuilder.FromDirectory( - this.PublishDir, - null, - this.Prefix, - this.Content); - - DebTask.EnsureDirectories(archiveEntries, includeRoot: false); - - archiveEntries = archiveEntries - .OrderBy(e => e.TargetPathWithFinalSlash, StringComparer.Ordinal) - .ToList(); - - using (var stream = File.Create(this.TarballPath)) - using (var gzipStream = new GZipStream(stream, CompressionMode.Compress)) - { - TarFileCreator.FromArchiveEntries(archiveEntries, gzipStream); - } - } - } -} diff --git a/DebUOS/Packaging.Targets/TaskItemExtensions.cs b/DebUOS/Packaging.Targets/TaskItemExtensions.cs deleted file mode 100644 index 5b094ae..0000000 --- a/DebUOS/Packaging.Targets/TaskItemExtensions.cs +++ /dev/null @@ -1,185 +0,0 @@ -using Microsoft.Build.Framework; -using System; -using System.IO; -using System.Linq; - -namespace Packaging.Targets -{ - /// - /// Provides extension methods for the interface. - /// - public static class TaskItemExtensions - { - /// - /// Gets a value indicating whether this item is copied to the publish directory or not. - /// - /// - /// The item for which to determine whether it is copied or not. - /// - /// - /// if the file is copied over; otherwise, . - /// - public static bool IsPublished(this ITaskItem item) - { - if (item == null) - { - return false; - } - - if (!item.MetadataNames.OfType().Contains("CopyToPublishDirectory")) - { - return false; - } - - var copyToPublishDirectoryValue = item.GetMetadata("CopyToPublishDirectory"); - CopyToDirectoryValue copyToPublishDirectory; - - if (!Enum.TryParse(copyToPublishDirectoryValue, out copyToPublishDirectory)) - { - return false; - } - - return copyToPublishDirectory != CopyToDirectoryValue.DoNotCopy; - } - - /// - /// Gets the path to where the file is published. - /// - /// - /// The item for which to determine the publish path. - /// - /// - /// The path to where the file is published. - /// - public static string GetPublishedPath(this ITaskItem item) - { - if (item == null) - { - return null; - } - - var link = item.GetMetadata("Link"); - if (!string.IsNullOrEmpty(link)) - { - return link.Replace("\\", "/"); - } - - var relativeDirectory = item.GetMetadata("RelativeDir"); - var filename = item.GetMetadata("FileName"); - var extension = item.GetMetadata("Extension"); - - return Path.Combine(relativeDirectory, $"{filename}{extension}").Replace("\\", "/"); - } - - /// - /// Gets the path of the file in the Linux filesystem. - /// - /// - /// The item for which to get the Linux path. - /// - /// - /// The path to the file on the Linux filesystem. - /// - public static string GetLinuxPath(this ITaskItem item) - { - return TryGetValue(item, "LinuxPath"); - } - - /// - /// Gets the file mode of the file in the Linux filesystem. - /// - /// - /// The item for which to get the file mode. - /// - /// - /// The file mode of the file on the Linux file system. - /// - public static string GetLinuxFileMode(this ITaskItem item) - { - return TryGetValue(item, "LinuxFileMode"); - } - - /// - /// Gets the Linux owner of the file. - /// - /// - /// The item for which to get the file owner. - /// - /// - /// The owner of the file. - /// - public static string GetOwner(this ITaskItem item) - { - return TryGetValue(item, "Owner", "root"); - } - - /// - /// Gets the Linux group of the file. - /// - /// - /// The item for which to get the file group. - /// - /// - /// The group of the file. - /// - public static string GetGroup(this ITaskItem item) - { - return TryGetValue(item, "Group", "root"); - } - - /// - /// Gets the version of the RPM dependency. - /// - /// - /// The task item which represents the RPM dependency. - /// - /// - /// The version of the dependency. - /// - public static string GetVersion(this ITaskItem item) - { - return TryGetValue(item, "Version", null); - } - - /// - /// Gets a value indicating whether the item should be removed when the - /// program is removed. - /// - /// - /// The item to inspect. - /// - /// - /// if the file should be removed; otherwise, - /// . - /// - public static bool GetRemoveOnUninstall(this ITaskItem item) - { - var valueString = TryGetValue(item, "RemoveOnUninstall", "false"); - bool value; - - if (!bool.TryParse(valueString, out value)) - { - return false; - } - - return value; - } - - private static string TryGetValue(ITaskItem item, string name, string @default = null) - { - if (item == null) - { - return @default; - } - - if (!item.MetadataNames.OfType().Contains(name)) - { - return @default; - } - - var linuxPath = item.GetMetadata(name); - - return linuxPath; - } - } -} diff --git a/DebUOS/Packaging.Targets/ZipTask.cs b/DebUOS/Packaging.Targets/ZipTask.cs deleted file mode 100644 index d6369c2..0000000 --- a/DebUOS/Packaging.Targets/ZipTask.cs +++ /dev/null @@ -1,68 +0,0 @@ -using ICSharpCode.SharpZipLib.Zip; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace Packaging.Targets -{ - public class ZipTask : Task - { - [Required] - public string PublishDir - { - get; - set; - } - - [Required] - public string ZipPath - { - get; - set; - } - - public override bool Execute() - { - this.Log.LogMessage(MessageImportance.Normal, "Creating zip archive '{0}' from folder '{1}'", this.ZipPath, this.PublishDir); - - this.CreateWindowsTarball(); - - this.Log.LogMessage(MessageImportance.Normal, "Created zip archive '{0}' from folder '{1}'", this.ZipPath, this.PublishDir); - return true; - } - - private void CreateWindowsTarball() - { - using (var stream = File.Create(this.ZipPath)) - using (var zipFile = ZipFile.Create(stream)) - { - this.AddDirectory(zipFile, this.PublishDir, string.Empty); - } - } - - private void AddDirectory(ZipFile zipFile, string directory, string directoryEntryName) - { - if (directoryEntryName != string.Empty) - { - zipFile.BeginUpdate(); - zipFile.AddDirectory(directoryEntryName); - zipFile.CommitUpdate(); - } - - foreach (var file in Directory.GetFiles(directory)) - { - zipFile.BeginUpdate(); - zipFile.Add(file, Path.Combine(directoryEntryName, Path.GetFileName(file))); - zipFile.CommitUpdate(); - } - - foreach (var child in Directory.GetDirectories(directory)) - { - this.AddDirectory(zipFile, child, Path.Combine(directoryEntryName, Path.GetFileName(child))); - } - } - } -} diff --git a/DebUOS/Packaging.Targets/build/Packaging.Targets.targets b/DebUOS/Packaging.Targets/build/Packaging.Targets.targets deleted file mode 100644 index c03a7ce..0000000 --- a/DebUOS/Packaging.Targets/build/Packaging.Targets.targets +++ /dev/null @@ -1,285 +0,0 @@ - - - - - - - - - - $(Version) - 1.0.0 - - $(TargetName) - $(PackagePrefix).$(PackageVersion).$(RuntimeIdentifier) - $(PackagePrefix).$(PackageVersion) - $(IntermediateOutputPath)$(PackageName) - $(TargetDir) - $([MSBuild]::EnsureTrailingSlash($(PackageDir)))$(PackageName) - false - false - $(Authors) - Anonymous <noreply@example.com> - $(Description) - $(PackageName) version $(PackageVersion) - $(Release) - true - $(AssemblyName)$(_NativeExecutableExtension) - - - - - - $(PackagePath).rpm - $(IntermediatePackagePath).cpio - /usr/share/$(PackagePrefix) - 0 - $(PackagePrefix) - $(PackagePrefix) - $(PackagePrefix) - $(Authors) - $(PackageDescription) - $(PackageProjectUrl) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $(PackagePath).deb - $(IntermediatePackagePath).deb.tar - $(IntermediatePackagePath).deb.tar.xz - /usr/share/$(PackagePrefix) - $(PackagePrefix) - $(PackagePrefix) - $(PackagePrefix) - misc - extra - $(PackageProjectUrl) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $(PackagePath).tar.gz - . - - - - - - - - - - - - $(PackagePath).zip - - - - - - - - - - - - $(TargetName) - $(TargetName) - $(TargetName)Feature - $(TargetName) - $(TargetName) - 1.0.0.0 - 1033 - - - - -nologo -dPublishDir="$(PublishDir)\" -dSetupProductName="$(SetupProductName)" -dSetupProductManufacturer="$(SetupProductManufacturer)" -dSetupFeatureId="$(SetupFeatureId)" -dSetupFeatureName="$(SetupFeatureName)" -dSetupInstallFolderName="$(SetupInstallFolderName)" -dSetupProductVersion="$(SetupProductVersion)" -dSetupProductLanguage="$(SetupProductLanguage)" - - - - C:\Program Files (x86)\WiX Toolset v3.10\ - $(IntermediateOutputPath)$(TargetName).harvest.wxs - $(IntermediateOutputPath)$(TargetName).harvest.wixobj - $(MSBuildThisFileDirectory)\Product.wxs - $(IntermediateOutputPath)$(TargetName).product.wixobj - $(TargetDir)$(TargetName).msi - $(WixInstallPath)bin\heat.exe - $(WixInstallPath)bin\candle.exe - $(WixInstallPath)bin\light.exe - - - - - - - - - - - - diff --git a/DebUOS/Packaging.Targets/build/Product.wxs b/DebUOS/Packaging.Targets/build/Product.wxs deleted file mode 100644 index 70c0721..0000000 --- a/DebUOS/Packaging.Targets/build/Product.wxs +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/dotnet-packaging.ruleset b/DebUOS/Packaging.Targets/dotnet-packaging.ruleset deleted file mode 100644 index 128cfc1..0000000 --- a/DebUOS/Packaging.Targets/dotnet-packaging.ruleset +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/DebUOS/Packaging.Targets/stylecop.json b/DebUOS/Packaging.Targets/stylecop.json deleted file mode 100644 index 125bda9..0000000 --- a/DebUOS/Packaging.Targets/stylecop.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", - "settings": { - "documentationRules": { - "companyName": "Quamotion" - } - } -} From 20cfa0e6ff34199399dc8b7b6b4ee9576c3e1c87 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 27 Dec 2023 11:50:38 +0800 Subject: [PATCH 06/74] =?UTF-8?q?=E5=88=9B=E5=BB=BA=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=92=8C=E6=B7=BB=E5=8A=A0=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Packaging.Targets/AssemblyAttributes.cs | 2 +- .../Build/package.props | 0 .../Build/package.targets | 0 .../Packing.DebUOS.Tool.csproj | 27 +++++++++++++++++++ DebUOS/Packing.DebUOS.Tool/Program.cs | 2 ++ .../{Program.cs => DebUOSPackageCreator.cs} | 5 +--- DebUOS/Packing.DebUOS/Packing.DebUOS.csproj | 26 +++--------------- DotNETBuild.sln | 15 +++++++++++ 8 files changed, 50 insertions(+), 27 deletions(-) rename DebUOS/{Packing.DebUOS => Packing.DebUOS.Tool}/Build/package.props (100%) rename DebUOS/{Packing.DebUOS => Packing.DebUOS.Tool}/Build/package.targets (100%) create mode 100644 DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj create mode 100644 DebUOS/Packing.DebUOS.Tool/Program.cs rename DebUOS/Packing.DebUOS/{Program.cs => DebUOSPackageCreator.cs} (65%) diff --git a/DebUOS/Packaging.Targets/AssemblyAttributes.cs b/DebUOS/Packaging.Targets/AssemblyAttributes.cs index ab9c458..d2d6a2f 100644 --- a/DebUOS/Packaging.Targets/AssemblyAttributes.cs +++ b/DebUOS/Packaging.Targets/AssemblyAttributes.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Packaging.Targets.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Packing.DebUOS")] \ No newline at end of file diff --git a/DebUOS/Packing.DebUOS/Build/package.props b/DebUOS/Packing.DebUOS.Tool/Build/package.props similarity index 100% rename from DebUOS/Packing.DebUOS/Build/package.props rename to DebUOS/Packing.DebUOS.Tool/Build/package.props diff --git a/DebUOS/Packing.DebUOS/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets similarity index 100% rename from DebUOS/Packing.DebUOS/Build/package.targets rename to DebUOS/Packing.DebUOS.Tool/Build/package.targets diff --git a/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj b/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj new file mode 100644 index 0000000..209e4cc --- /dev/null +++ b/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj @@ -0,0 +1,27 @@ + + + + Exe + net6.0 + enable + enable + + + + + true + + + + true + MIT + true + + + + + + + + + diff --git a/DebUOS/Packing.DebUOS.Tool/Program.cs b/DebUOS/Packing.DebUOS.Tool/Program.cs new file mode 100644 index 0000000..3751555 --- /dev/null +++ b/DebUOS/Packing.DebUOS.Tool/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); diff --git a/DebUOS/Packing.DebUOS/Program.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs similarity index 65% rename from DebUOS/Packing.DebUOS/Program.cs rename to DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index df69a24..7da65d6 100644 --- a/DebUOS/Packing.DebUOS/Program.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -6,9 +6,6 @@ namespace Packing.DebUOS; -internal class Program +public class DebUOSPackageCreator { - public static void Main(string[] args) - { - } } diff --git a/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj b/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj index 19722a0..f8984fa 100644 --- a/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj +++ b/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj @@ -1,34 +1,17 @@ - Exe net6.0 enable - True - - - tools - - true - - - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - true - MIT - true + + false - - all - - - all - - + all @@ -37,7 +20,6 @@ - - + diff --git a/DotNETBuild.sln b/DotNETBuild.sln index a977a60..3a449ad 100644 --- a/DotNETBuild.sln +++ b/DotNETBuild.sln @@ -95,6 +95,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Packing.DebUOS", "DebUOS\Pa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Packaging.Targets", "DebUOS\Packaging.Targets\Packaging.Targets.csproj", "{C69F9A99-8110-4379-A22C-176B3E05E481}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Packing.DebUOS.Tool", "DebUOS\Packing.DebUOS.Tool\Packing.DebUOS.Tool.csproj", "{4FFC6853-8CF6-4C87-9EA7-B04811DCD051}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -501,6 +503,18 @@ Global {C69F9A99-8110-4379-A22C-176B3E05E481}.Release|x64.Build.0 = Release|Any CPU {C69F9A99-8110-4379-A22C-176B3E05E481}.Release|x86.ActiveCfg = Release|Any CPU {C69F9A99-8110-4379-A22C-176B3E05E481}.Release|x86.Build.0 = Release|Any CPU + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Debug|x64.ActiveCfg = Debug|Any CPU + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Debug|x64.Build.0 = Debug|Any CPU + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Debug|x86.ActiveCfg = Debug|Any CPU + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Debug|x86.Build.0 = Debug|Any CPU + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Release|Any CPU.Build.0 = Release|Any CPU + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Release|x64.ActiveCfg = Release|Any CPU + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Release|x64.Build.0 = Release|Any CPU + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Release|x86.ActiveCfg = Release|Any CPU + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -523,6 +537,7 @@ Global {4360E6F1-680C-45D7-A4E0-1663A237709A} = {3C8DD4B9-55DB-450E-B06A-B7C4EB3A9CF1} {209825D6-7821-4E0E-B3C8-E3504FF65B36} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} {C69F9A99-8110-4379-A22C-176B3E05E481} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} + {4FFC6853-8CF6-4C87-9EA7-B04811DCD051} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A6E377C6-EDE0-4135-951F-78C62B63DE96} From 0a14fb8b07f6f81cb4597821d28e0c1709c90963 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 27 Dec 2023 14:25:20 +0800 Subject: [PATCH 07/74] =?UTF-8?q?=E5=BC=80=E5=A7=8B=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E4=BF=A1=E6=81=AF=E5=B0=9D=E8=AF=95=E8=BF=9B?= =?UTF-8?q?=E5=85=A5=E8=B0=83=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.Targets/Packaging.Targets.csproj | 2 ++ DebUOS/Packing.DebUOS.Tool/Build/package.targets | 4 ++++ DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj | 8 +++++++- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 2 ++ DebUOS/Packing.DebUOS/Packing.DebUOS.csproj | 2 ++ build/Version.props | 2 +- 6 files changed, 18 insertions(+), 2 deletions(-) diff --git a/DebUOS/Packaging.Targets/Packaging.Targets.csproj b/DebUOS/Packaging.Targets/Packaging.Targets.csproj index 7a5892c..4800398 100644 --- a/DebUOS/Packaging.Targets/Packaging.Targets.csproj +++ b/DebUOS/Packaging.Targets/Packaging.Targets.csproj @@ -5,6 +5,8 @@ True false + + true diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index c1df222..0b52699 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -1,2 +1,6 @@ + + + + \ No newline at end of file diff --git a/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj b/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj index 209e4cc..4247f9d 100644 --- a/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj +++ b/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj @@ -7,6 +7,7 @@ enable + false true @@ -16,12 +17,17 @@ true MIT true + + true - + + + all + diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index 7da65d6..0b35395 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -3,9 +3,11 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using dotnetCampus.MSBuildUtils; namespace Packing.DebUOS; public class DebUOSPackageCreator { + } diff --git a/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj b/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj index f8984fa..2308cd9 100644 --- a/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj +++ b/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj @@ -5,6 +5,8 @@ enable false + + true diff --git a/build/Version.props b/build/Version.props index 4c320dc..75f05cb 100644 --- a/build/Version.props +++ b/build/Version.props @@ -1,5 +1,5 @@ - 1.2.1 + 1.2.1-alpha02 \ No newline at end of file From 7ebb3ab86ba10eb39682457334db4acffa621448 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 27 Dec 2023 14:54:47 +0800 Subject: [PATCH 08/74] =?UTF-8?q?=E8=B0=83=E8=AF=95=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E6=89=93=E5=8C=85=E6=97=B6=E6=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Build/package.props | 3 +++ .../Packing.DebUOS.Tool/Build/package.targets | 18 +++++++++++++++--- build/Version.props | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.props b/DebUOS/Packing.DebUOS.Tool/Build/package.props index c1df222..fc89edd 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.props +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.props @@ -1,2 +1,5 @@ + + $([MSBuild]::NormalizePath($(IntermediateOutputPath), 'DebUOSPacking')) + \ No newline at end of file diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index 0b52699..f6fe5e6 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -1,6 +1,18 @@ - - - + + + + + + + + + $([MSBuild]::NormalizePath($(DebUOSPackingWorkFolder), 'DebUOSPackingArgs.coin')) + + + + + + \ No newline at end of file diff --git a/build/Version.props b/build/Version.props index 75f05cb..97e3164 100644 --- a/build/Version.props +++ b/build/Version.props @@ -1,5 +1,5 @@ - 1.2.1-alpha02 + 1.2.1-alpha07 \ No newline at end of file From c5b629636fe0722eba822b7911da088464cea9cd Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 27 Dec 2023 14:57:42 +0800 Subject: [PATCH 09/74] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=AD=A3=E7=A1=AE?= =?UTF-8?q?=E7=9A=84=E6=89=93=E5=8C=85=E5=B7=A5=E4=BD=9C=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=A4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Build/package.props | 4 +--- DebUOS/Packing.DebUOS.Tool/Build/package.targets | 4 ++++ build/Version.props | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.props b/DebUOS/Packing.DebUOS.Tool/Build/package.props index fc89edd..4de98b5 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.props +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.props @@ -1,5 +1,3 @@ - - $([MSBuild]::NormalizePath($(IntermediateOutputPath), 'DebUOSPacking')) - + \ No newline at end of file diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index f6fe5e6..cadb0c3 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -7,6 +7,10 @@ + + $([MSBuild]::NormalizePath($(IntermediateOutputPath), 'DebUOSPacking')) + + $([MSBuild]::NormalizePath($(DebUOSPackingWorkFolder), 'DebUOSPackingArgs.coin')) diff --git a/build/Version.props b/build/Version.props index 97e3164..9fa5f86 100644 --- a/build/Version.props +++ b/build/Version.props @@ -1,5 +1,5 @@ - 1.2.1-alpha07 + 1.2.1-alpha09 \ No newline at end of file From 4b1f45e24ee1c498960122055050e9870def3b83 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 27 Dec 2023 14:59:49 +0800 Subject: [PATCH 10/74] =?UTF-8?q?=E5=BC=80=E5=A7=8B=E5=86=99=E5=85=A5?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Build/package.targets | 13 +++++++------ build/Version.props | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index cadb0c3..7a8f4a8 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -1,10 +1,4 @@ - - - - - - @@ -16,7 +10,14 @@ + + + + + + + \ No newline at end of file diff --git a/build/Version.props b/build/Version.props index 9fa5f86..cac44fb 100644 --- a/build/Version.props +++ b/build/Version.props @@ -1,5 +1,5 @@ - 1.2.1-alpha09 + 1.2.1-alpha10 \ No newline at end of file From 3eb14e184c8499cbad9d2aa93f1b3274878c992b Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 27 Dec 2023 15:18:05 +0800 Subject: [PATCH 11/74] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Build/package.targets | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index 7a8f4a8..1c16dcb 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -16,6 +16,17 @@ + + + + + + + + + + + From ad266b604d58a41411027a2566376548c0a9e5a4 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 28 Dec 2023 15:43:16 +0800 Subject: [PATCH 12/74] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E7=9A=84=E6=89=93=E5=8C=85=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 但是解压失败 dpkg: error processing archive DebPath.deb (--install): unable to create '/opt/apps/org.dotnetcampust.unofiledownloader/entries/applications/org.dotnetcampust.unofiledownloader.desktop.dpkg-new' (while processing './opt/apps/org.dotnetcampust.unofiledownloader/entries/applications/org.dotnetcampust.unofiledownloader.desktop'): No such file or directory dpkg-deb: error: paste subprocess was killed by signal (Broken pipe) --- DebUOS/Packaging.Targets/ArchiveBuilder.cs | 449 ++++++++++++++++++ DebUOS/Packing.DebUOS/DebUOSConfiguration.cs | 14 + DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 105 +++- 3 files changed, 567 insertions(+), 1 deletion(-) create mode 100644 DebUOS/Packaging.Targets/ArchiveBuilder.cs create mode 100644 DebUOS/Packing.DebUOS/DebUOSConfiguration.cs diff --git a/DebUOS/Packaging.Targets/ArchiveBuilder.cs b/DebUOS/Packaging.Targets/ArchiveBuilder.cs new file mode 100644 index 0000000..7ae18ef --- /dev/null +++ b/DebUOS/Packaging.Targets/ArchiveBuilder.cs @@ -0,0 +1,449 @@ +//using Microsoft.Build.Framework; +//using Microsoft.Build.Utilities; +using Packaging.Targets.IO; +using Packaging.Targets.Rpm; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace Packaging.Targets +{ + /// + /// Creates a list of objects based on the publish directory of a .NET Core application. + /// + internal class ArchiveBuilder + { + private IFileAnalyzer fileAnayzer; + private uint inode = 0; + + /// + /// Initializes a new instance of the class. + /// + public ArchiveBuilder() + { + this.fileAnayzer = new FileAnalyzer(); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A which can extract item metadata. + /// + public ArchiveBuilder(IFileAnalyzer analyzer) + { + if (analyzer == null) + { + throw new ArgumentNullException(nameof(analyzer)); + } + + this.fileAnayzer = analyzer; + } + + ///// + ///// Gets or sets a to which status messages can be written. + ///// + //public TaskLoggingHelper Log + //{ + // get; + // set; + //} + + /// + /// Extracts the objects from a CPIO file. + /// + /// + /// The CPIO file from which to extract the entries. + /// + /// + /// A list of objects representing the data in the CPIO file. + /// + public List FromCpio(CpioFile file) + { + List value = new List(); + byte[] buffer = new byte[1024]; + byte[] fileHeader = null; + + while (file.Read()) + { + fileHeader = null; + + ArchiveEntry entry = new ArchiveEntry() + { + FileSize = file.EntryHeader.FileSize, + Group = "root", + Owner = "root", + Inode = file.EntryHeader.Ino, + Mode = file.EntryHeader.FileMode, + Modified = file.EntryHeader.LastModified, + TargetPath = file.FileName, + Type = ArchiveEntryType.None, + LinkTo = string.Empty, + Sha256 = Array.Empty(), + SourceFilename = null, + IsAscii = true + }; + + if (entry.Mode.HasFlag(LinuxFileMode.S_IFREG) && !entry.Mode.HasFlag(LinuxFileMode.S_IFLNK)) + { + using (var fileStream = file.Open()) + using (var hasher = IncrementalHash.CreateHash(HashAlgorithmName.SHA256)) + { + int read; + + while (true) + { + read = fileStream.Read(buffer, 0, buffer.Length); + + if (fileHeader == null) + { + fileHeader = new byte[read]; + Buffer.BlockCopy(buffer, 0, fileHeader, 0, read); + } + + hasher.AppendData(buffer, 0, read); + entry.IsAscii = entry.IsAscii && fileHeader.All(c => c < 128); + + if (read < buffer.Length) + { + break; + } + } + + entry.Sha256 = hasher.GetHashAndReset(); + } + + entry.Type = this.GetArchiveEntryType(fileHeader); + } + else if (entry.Mode.HasFlag(LinuxFileMode.S_IFLNK)) + { + using (var fileStream = file.Open()) + using (var reader = new StreamReader(fileStream, Encoding.UTF8)) + { + entry.LinkTo = reader.ReadToEnd(); + } + } + else + { + file.Skip(); + } + + if (entry.Mode.HasFlag(LinuxFileMode.S_IFDIR)) + { + entry.FileSize = 0x1000; + } + + if (entry.TargetPath.StartsWith(".")) + { + entry.TargetPath = entry.TargetPath.Substring(1); + } + + value.Add(entry); + } + + return value; + } + + //public Collection FromLinuxFolders(ITaskItem[] metadata) + //{ + // Collection value = new Collection(); + + // // This can be null if the user did not define any folders. + // // In that case: nothing to do. + // if (metadata != null) + // { + // foreach (var folder in metadata) + // { + // var path = folder.ItemSpec.Replace("\\", "/"); + + // // Default file mode + // LinuxFileMode 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; + // mode = this.GetFileMode(path, folder, mode); + + // // Write out an entry for the directory + // ArchiveEntry directoryEntry = new ArchiveEntry() + // { + // FileSize = 0x00001000, + // Sha256 = Array.Empty(), + // Mode = mode, + // Modified = DateTime.Now, + // Group = folder.GetGroup(), + // Owner = folder.GetOwner(), + // Inode = this.inode++, + // TargetPath = path, + // LinkTo = string.Empty, + // RemoveOnUninstall = folder.GetRemoveOnUninstall() + // }; + + // value.Add(directoryEntry); + // } + // } + + // return value; + //} + + /// + /// Extracts the objects from a directory. + /// + /// + /// The directory from which to extract the entries. + /// + /// + /// The prefix of the target directory. + /// + /// + /// Additional metadata to use. + /// + /// + /// A list of objects representing the data in the directory. + /// + public List FromDirectory(string directory, string? appHost, string prefix/*, ITaskItem[] metadata*/) + { + List value = new List(); + 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(), + }); + } + + return value; + } + + protected void AddDirectory(string directory, string relativePath, string prefix, List value/*, ITaskItem[] metadata*/) + { + this.inode++; + + // The order in which the files appear in the cpio archive is important; if this is not respected xzdio + // will report errors like: + // error: unpacking of archive failed on file ./usr/share/quamotion/mscorlib.dll: cpio: Archive file not in header + var entries = Directory.GetFileSystemEntries(directory).OrderBy(e => Directory.Exists(e) ? e + "/" : e, StringComparer.Ordinal).ToArray(); + + foreach (var entry in entries) + { + if (File.Exists(entry)) + { + this.AddFile(entry, relativePath + Path.GetFileName(entry), prefix, value/*, metadata*/); + } + else + { + this.AddDirectory(entry, relativePath + Path.GetFileName(entry) + "/", prefix + "/" + Path.GetFileName(entry), value/*, metadata*/); + } + } + } + + protected void AddFile(string entry, string relativePath, string prefix, List value/*, ITaskItem[] metadata*/) + { + var fileName = Path.GetFileName(entry); + + byte[] fileHeader = new byte[32]; + byte[] hash = null; + byte[] md5hash = null; + byte[] buffer = new byte[1024]; + bool isAscii = true; + + //var fileMetadata = metadata.SingleOrDefault(m => m.IsPublished() && string.Equals(relativePath, m.GetPublishedPath())); + + using (Stream fileStream = File.OpenRead(entry)) + { + // Skip hidden and empty files - this would case rpmlint errors. + if (fileName.StartsWith(".")) + { + //this.Log.LogWarning($"Ignoring file {relativePath} because it starts with the '.' character and is considered a hidden file."); + return; + } + + // 可能有一些空文件存在,这是合理的 + //if (fileStream.Length == 0) + //{ + // //this.Log.LogWarning($"Ignoring file {relativePath} because it is empty."); + // return; + //} + + //using (var hasher = IncrementalHash.CreateHash(HashAlgorithmName.SHA256)) + //using (var md5hasher = IncrementalHash.CreateHash(HashAlgorithmName.MD5)) + //{ + // int read; + + // while (true) + // { + // read = fileStream.Read(buffer, 0, buffer.Length); + + // if (fileHeader == null) + // { + // fileHeader = new byte[read]; + // Buffer.BlockCopy(buffer, 0, fileHeader, 0, read); + // } + + // hasher.AppendData(buffer, 0, read); + // md5hasher.AppendData(buffer, 0, read); + // isAscii = isAscii && buffer.All(c => c < 128); + + // if (read < buffer.Length) + // { + // break; + // } + // } + + // hash = hasher.GetHashAndReset(); + // md5hash = md5hasher.GetHashAndReset(); + //} + + // Only support ELF32 and ELF64 colors; otherwise default to BLACK. + ArchiveEntryType entryType = this.GetArchiveEntryType(fileHeader); + + var mode = LinuxFileMode.S_IROTH | LinuxFileMode.S_IRGRP | LinuxFileMode.S_IRUSR | LinuxFileMode.S_IFREG; + + if (entryType == ArchiveEntryType.Executable32 || entryType == ArchiveEntryType.Executable64) + { + mode |= LinuxFileMode.S_IXOTH | LinuxFileMode.S_IXGRP | LinuxFileMode.S_IWUSR | LinuxFileMode.S_IXUSR; + } + + // If a Linux path has been specified, use that one, else, use the default one based on the prefix + // + current file name. + string name = null; //fileMetadata?.GetLinuxPath(); + + if (name == null) + { + if (!string.IsNullOrEmpty(prefix)) + { + name = prefix + "/" + fileName; + } + else + { + name = fileName; + } + } + + string linkTo = string.Empty; + + if (mode.HasFlag(LinuxFileMode.S_IFLNK)) + { + // Find the link text + int stringEnd = 0; + + while (stringEnd < fileHeader.Length - 1 && fileHeader[stringEnd] != 0) + { + stringEnd++; + } + + linkTo = Encoding.UTF8.GetString(fileHeader, 0, stringEnd + 1); + hash = new byte[] { }; + } + + mode = this.GetFileMode(name, /*fileMetadata,*/ mode); + + ArchiveEntry archiveEntry = new ArchiveEntry() + { + FileSize = (uint)fileStream.Length, + //Group = fileMetadata.GetGroup(), + //Owner = fileMetadata.GetOwner(), + Modified = File.GetLastWriteTimeUtc(entry), + SourceFilename = entry, + TargetPath = name, + // 可以忽略这两个的计算 + //Sha256 = hash, + //Md5Hash = md5hash, + Type = entryType, + LinkTo = linkTo, + Inode = this.inode++, + IsAscii = isAscii, + Mode = mode + }; + + value.Add(archiveEntry); + } + } + + private ArchiveEntryType GetArchiveEntryType(byte[] fileHeader) + { + if (ElfFile.IsElfFile(fileHeader)) + { + ElfHeader elfHeader = ElfFile.ReadHeader(fileHeader); + + if (elfHeader.@class == ElfClass.Elf32) + { + return ArchiveEntryType.Executable32; + } + else + { + return ArchiveEntryType.Executable64; + } + } + + return ArchiveEntryType.None; + } + + /// + /// Gets the file mode for a file or directory entry, based on a default value + /// and the value of the LinuxFileMode attribute, if set. + /// + /// + /// The name (path) of the entry. Only used in error messages. + /// + /// + /// The metadata for the current entry. + /// + /// + /// The default mode. This mode is used to determine the file type (directory/file), + /// and when no LinuxFileMode value is specified. + /// + /// + /// The for the current entry. + /// + private LinuxFileMode GetFileMode(string name, /*ITaskItem metadata, */LinuxFileMode defaultMode) + { + return defaultMode; + + //LinuxFileMode mode = defaultMode; + //LinuxFileMode defaultFileTypeMask = defaultMode & LinuxFileMode.FileTypeMask; + + //// If the user has chosen to override the file node, respect that + //var overridenFileMode = metadata?.GetLinuxFileMode(); + + //if (overridenFileMode != null) + //{ + // // We expect the user to specify the file mode in its octal representation, + // // such as 755. We don't expect users to specify the higher bits (e.g. + // // S_IFREG). + // try + // { + // mode = (LinuxFileMode)Convert.ToUInt32(overridenFileMode, 8); + + // LinuxFileMode fileType = mode & LinuxFileMode.FileTypeMask; + + // if (fileType != LinuxFileMode.None && fileType != defaultFileTypeMask) + // { + // //this.Log.LogWarning($"An invalid file type of '{fileType}' has been set for file '{name}'. The file type will be reset to {defaultFileTypeMask}."); + // } + + // // Override the file type mask and hardcode it to the file type mask from the default mode. + // // In practice this will ensure the S_IFREG or S_IFDIR flag is set. + // mode = (mode & ~LinuxFileMode.FileTypeMask) | defaultFileTypeMask; + // } + // catch (Exception) + // { + // throw new Exception($"Could not parse the file mode '{overridenFileMode}' for file '{name}'. Make sure to set the LinuxFileMode attriubute to an octal representation of a Unix file mode."); + // } + //} + + //return mode; + } + } +} diff --git a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs new file mode 100644 index 0000000..5b681bf --- /dev/null +++ b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs @@ -0,0 +1,14 @@ +using dotnetCampus.Configurations; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Packing.DebUOS; + +class DebUOSConfiguration : Configuration +{ + +} diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index 0b35395..9a27d72 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -1,13 +1,116 @@ using System; using System.Collections.Generic; +using System.IO; +using System.IO.Compression; using System.Linq; using System.Text; using System.Threading.Tasks; using dotnetCampus.MSBuildUtils; +using Packaging.Targets; +using Packaging.Targets.IO; + namespace Packing.DebUOS; public class DebUOSPackageCreator { - + public void Execute() + { + ArchiveBuilder archiveBuilder = new ArchiveBuilder() + { + }; + + var optFolder = Path.Combine(WorkFolder, "opt"); + + var archiveEntries = archiveBuilder.FromDirectory( + optFolder, + null, + "/opt"); + archiveEntries = archiveEntries + .OrderBy(e => e.TargetPathWithFinalSlash, StringComparer.Ordinal) + .ToList(); + + 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)) + { + TarFileCreator.FromArchiveEntries(archiveEntries, tarStream); + tarStream.Position = 0; + + var debTarXzPath = Path.Combine(WorkFolder, "deb.tar.xz"); + + // 由于 XZOutputStream 类质量低劣(连当前位置都不知道,需要 Dispose 才能完成压缩等等) + // 因此需要先 Dispose 再重新打开 + // 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(debTarXzPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) + using (var xzStream = new XZOutputStream(tarXzStream)) + { + tarStream.CopyTo(xzStream); + } + + // 重新打开 + using (var tarXzStream = File.Open(debTarXzPath, FileMode.Open, FileAccess.Read, FileShare.None)) + { + var pkgPackageFormatVersion = new Version(2, 0); + + ArFileCreator.WriteMagic(targetStream); + ArFileCreator.WriteEntry(targetStream, "debian-binary", ArFileMode, pkgPackageFormatVersion + "\n"); + WriteControl(targetStream); + ArFileCreator.WriteEntry(targetStream, "data.tar.xz", ArFileMode, tarXzStream); + } + } + } + + private void WriteControl(Stream targetStream) + { + var controlTar = new MemoryStream(); + WriteControlEntry(controlTar, "./"); + + var controlFile = Path.Combine(WorkFolder, "DEBIAN", "control"); + var controlFileText = File.ReadAllText(controlFile); + + WriteControlEntry(controlTar, "./control", controlFileText); + TarFileCreator.WriteTrailer(controlTar); + controlTar.Seek(0, SeekOrigin.Begin); + + var controlTarGz = new MemoryStream(); + using (var gzStream = new GZipStream(controlTarGz, CompressionMode.Compress, true)) + { + controlTar.CopyTo(gzStream); + } + + controlTarGz.Seek(0, SeekOrigin.Begin); + ArFileCreator.WriteEntry(targetStream, "control.tar.gz", ArFileMode, controlTarGz); + } + + private static void WriteControlEntry(Stream tar, string name, string data = null, LinuxFileMode? fileMode = null) + { + var s = (data != null) ? new MemoryStream(Encoding.UTF8.GetBytes(data)) : new MemoryStream(); + var mode = fileMode ?? LinuxFileMode.S_IRUSR | LinuxFileMode.S_IWUSR | + LinuxFileMode.S_IRGRP | LinuxFileMode.S_IROTH; + mode |= data == null + ? LinuxFileMode.S_IFDIR | LinuxFileMode.S_IXUSR | LinuxFileMode.S_IXGRP | LinuxFileMode.S_IXOTH + : LinuxFileMode.S_IFREG; + var hdr = new TarHeader + { + FileMode = mode, + FileName = name, + FileSize = (uint) s.Length, + GroupName = "root", + UserName = "root", + LastModified = DateTimeOffset.UtcNow, + Magic = "ustar", + TypeFlag = data == null ? TarTypeFlag.DirType : TarTypeFlag.RegType, + }; + TarFileCreator.WriteEntry(tar, hdr, s); + } + + + public string WorkFolder { set; get; } = @"C:\lindexi\Work\"; + public string DebPath { get; init; } = "DebPath.deb"; + public string DebTarPath { get; init; } = "DebTarPath.tar"; + + private const LinuxFileMode ArFileMode = LinuxFileMode.S_IRUSR | LinuxFileMode.S_IWUSR | LinuxFileMode.S_IRGRP | + LinuxFileMode.S_IROTH | LinuxFileMode.S_IFREG; } From 7da0096edcb826539ebea07223f6eb5ae284e6ca Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 28 Dec 2023 15:43:26 +0800 Subject: [PATCH 13/74] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BF=AB=E9=80=9F?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Program.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Program.cs b/DebUOS/Packing.DebUOS.Tool/Program.cs index 3751555..8257cf5 100644 --- a/DebUOS/Packing.DebUOS.Tool/Program.cs +++ b/DebUOS/Packing.DebUOS.Tool/Program.cs @@ -1,2 +1,15 @@ // See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); + +using dotnetCampus.Configurations; +using dotnetCampus.Configurations.Core; + +using Packing.DebUOS; + +//var argsFilePath = args[0]; +//var fileConfigurationRepo = ConfigurationFactory.FromFile(argsFilePath,RepoSyncingBehavior.Static); +//var appConfigurator = fileConfigurationRepo.CreateAppConfigurator(); + +var debUosPackageCreator = new DebUOSPackageCreator(); +debUosPackageCreator.Execute(); + +Console.Read(); \ No newline at end of file From 849470013846d8ba1679c90a0dd908b972b6bc20 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 28 Dec 2023 16:21:06 +0800 Subject: [PATCH 14/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=89=BE=E4=B8=8D?= =?UTF-8?q?=E5=88=B0=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dpkg: error processing archive DebPath.deb (--install): unable to create '/opt/apps/org.dotnetcampust.unofiledownloader/entries/applications/org.dotnetcampust.unofiledownloader.desktop.dpkg-new' (while processing './opt/apps/org.dotnetcampust.unofiledownloader/entries/ --- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index 9a27d72..a5cff1f 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -26,6 +26,9 @@ public void Execute() optFolder, null, "/opt"); + + EnsureDirectories(archiveEntries); + archiveEntries = archiveEntries .OrderBy(e => e.TargetPathWithFinalSlash, StringComparer.Ordinal) .ToList(); @@ -106,6 +109,70 @@ private static void WriteControlEntry(Stream tar, string name, string data = nul TarFileCreator.WriteEntry(tar, hdr, s); } + 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); + } public string WorkFolder { set; get; } = @"C:\lindexi\Work\"; public string DebPath { get; init; } = "DebPath.deb"; From 37dbd7e4b0979eaeb2477d1f2a7b251064092e18 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 28 Dec 2023 16:21:15 +0800 Subject: [PATCH 15/74] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.Targets/ArchiveBuilder.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/DebUOS/Packaging.Targets/ArchiveBuilder.cs b/DebUOS/Packaging.Targets/ArchiveBuilder.cs index 7ae18ef..7b02d26 100644 --- a/DebUOS/Packaging.Targets/ArchiveBuilder.cs +++ b/DebUOS/Packaging.Targets/ArchiveBuilder.cs @@ -252,7 +252,7 @@ protected void AddFile(string entry, string relativePath, string prefix, List Date: Thu, 28 Dec 2023 17:27:14 +0800 Subject: [PATCH 16/74] =?UTF-8?q?=E6=8F=90=E4=BE=9B=E6=89=93=E5=8C=85?= =?UTF-8?q?=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.Targets/ArchiveBuilder.cs | 14 +++++----- DebUOS/Packing.DebUOS.Tool/Program.cs | 3 ++- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 27 +++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/DebUOS/Packaging.Targets/ArchiveBuilder.cs b/DebUOS/Packaging.Targets/ArchiveBuilder.cs index 7b02d26..54156ec 100644 --- a/DebUOS/Packaging.Targets/ArchiveBuilder.cs +++ b/DebUOS/Packaging.Targets/ArchiveBuilder.cs @@ -17,15 +17,15 @@ namespace Packaging.Targets /// internal class ArchiveBuilder { - private IFileAnalyzer fileAnayzer; - private uint inode = 0; + private IFileAnalyzer _fileAnayzer; + private uint _inode = 0; /// /// Initializes a new instance of the class. /// public ArchiveBuilder() { - this.fileAnayzer = new FileAnalyzer(); + this._fileAnayzer = new FileAnalyzer(); } /// @@ -41,7 +41,7 @@ public ArchiveBuilder(IFileAnalyzer analyzer) throw new ArgumentNullException(nameof(analyzer)); } - this.fileAnayzer = analyzer; + this._fileAnayzer = analyzer; } ///// @@ -218,7 +218,7 @@ public List FromDirectory(string directory, string? appHost, strin Owner = "root", TargetPath = $"/usr/local/bin/{appHost}", LinkTo = $"{prefix}/{appHost}", - Inode = this.inode++, + Inode = this._inode++, Sha256 = Array.Empty(), }); } @@ -228,7 +228,7 @@ public List FromDirectory(string directory, string? appHost, strin protected void AddDirectory(string directory, string relativePath, string prefix, List value/*, ITaskItem[] metadata*/) { - this.inode++; + this._inode++; // The order in which the files appear in the cpio archive is important; if this is not respected xzdio // will report errors like: @@ -362,7 +362,7 @@ protected void AddFile(string entry, string relativePath, string prefix, List e.TargetPathWithFinalSlash, StringComparer.Ordinal) .ToList(); - 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)) + using (var targetStream = outputDebFile.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.None)) + using (var tarStream = File.Open(debTarFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) { TarFileCreator.FromArchiveEntries(archiveEntries, tarStream); tarStream.Position = 0; - var debTarXzPath = Path.Combine(WorkFolder, "deb.tar.xz"); - // 由于 XZOutputStream 类质量低劣(连当前位置都不知道,需要 Dispose 才能完成压缩等等) // 因此需要先 Dispose 再重新打开 // XZOutputStream class has low quality (doesn't even know it's current position, @@ -55,22 +58,22 @@ public void Execute() // 重新打开 using (var tarXzStream = File.Open(debTarXzPath, FileMode.Open, FileAccess.Read, FileShare.None)) { - var pkgPackageFormatVersion = new Version(2, 0); + var pkgPackageFormatVersion = new Version(2, 0); ArFileCreator.WriteMagic(targetStream); ArFileCreator.WriteEntry(targetStream, "debian-binary", ArFileMode, pkgPackageFormatVersion + "\n"); - WriteControl(targetStream); + WriteControl(packingFolder, targetStream); ArFileCreator.WriteEntry(targetStream, "data.tar.xz", ArFileMode, tarXzStream); } } } - private void WriteControl(Stream targetStream) + private void WriteControl(DirectoryInfo packingFolder, Stream targetStream) { var controlTar = new MemoryStream(); WriteControlEntry(controlTar, "./"); - var controlFile = Path.Combine(WorkFolder, "DEBIAN", "control"); + var controlFile = Path.Combine(packingFolder.FullName, "DEBIAN", "control"); var controlFileText = File.ReadAllText(controlFile); WriteControlEntry(controlTar, "./control", controlFileText); @@ -174,10 +177,6 @@ void EnsureDir(string dirPath) entries.AddRange(toAdd); } - public string WorkFolder { set; get; } = @"C:\lindexi\Work\"; - public string DebPath { get; init; } = "DebPath.deb"; - public string DebTarPath { get; init; } = "DebTarPath.tar"; - private const LinuxFileMode ArFileMode = LinuxFileMode.S_IRUSR | LinuxFileMode.S_IWUSR | LinuxFileMode.S_IRGRP | LinuxFileMode.S_IROTH | LinuxFileMode.S_IFREG; } From eece20db7f0ad3deedb81a54d6820418bad1004c Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 28 Dec 2023 17:55:31 +0800 Subject: [PATCH 17/74] =?UTF-8?q?=E6=8F=90=E4=BE=9B=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/DebUOSConfiguration.cs | 244 +++++++++++++++++- .../DebUOSPackageFileStructCreator.cs | 6 + 2 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs diff --git a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs index 5b681bf..17b8d82 100644 --- a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs +++ b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs @@ -8,7 +8,249 @@ namespace Packing.DebUOS; -class DebUOSConfiguration : Configuration +public class DebUOSConfiguration : Configuration { + public DebUOSConfiguration() : base("") + { + } + public string? DebControlFile + { + set => SetValue(value); + get => GetString(); + } + + public string? DebInfoFile + { + set => SetValue(value); + get => GetString(); + } + + public string? DebDesktopFile + { + set => SetValue(value); + get => GetString(); + } + + public string? AppId + { + set => SetValue(value); + get => GetString(); + } + + public string? Version + { + set => SetValue(value); + get => GetString(); + } + + public string? DebControlSection + { + set => SetValue(value); + get => GetString(); + } + + public string DebControlPriority + { + set => SetValue(value); + get => GetString() ?? "optional"; + } + + public string Architecture + { + set => SetValue(value); + get => GetString() ?? "amd64"; + } + + public string DebControlMultiArch + { + set => SetValue(value); + get => GetString() ?? "foreign"; + } + + public string DebControlBuildDepends + { + set => SetValue(value); + get => GetString() ?? "debhelper (>=9)"; + } + + public string DebControlStandardsVersion + { + set => SetValue(value); + get => GetString() ?? "3.9.6"; + } + + public string? DebControlMaintainer + { + set => SetValue(value); + get => GetString(); + } + + public string? DebControlHomepage + { + set => SetValue(value); + get => GetString(); + } + + public string? DebControlDescription + { + set => SetValue(value); + get => GetString(); + } + + public string? AppName + { + set => SetValue(value); + get => GetString(); + } + + public string? InfoPermissions + { + set => SetValue(value); + get => GetString(); + } + + public string? AppNameZhCN + { + set => SetValue(value); + get => GetString(); + } + + public string? DesktopCategories + { + set => SetValue(value); + get => GetString(); + } + + public string? DesktopKeywords + { + set => SetValue(value); + get => GetString(); + } + + public string? DesktopKeywordsZhCN + { + set => SetValue(value); + get => GetString(); + } + + public string? DesktopComment + { + set => SetValue(value); + get => GetString(); + } + + public string? DesktopCommentZhCN + { + set => SetValue(value); + get => GetString(); + } + + public string? DesktopExec + { + set => SetValue(value); + get => GetString(); + } + + public string? DesktopIcon + { + set => SetValue(value); + get => GetString(); + } + + public string DesktopType + { + set => SetValue(value); + get => GetString() ?? "Application"; + } + + public bool DesktopTerminal + { + set => SetValue(value); + get => GetBoolean() ?? false; + } + + public bool DesktopStartupNotify + { + set => SetValue(value); + get => GetBoolean() ?? true; + } + + public string? DesktopMimeType + { + set => SetValue(value); + get => GetString(); + } + + public string PackingFolder + { + set => SetValue(value); + get => GetString(); + } + + public string? WorkingFolder + { + set => SetValue(value); + get => GetString(); + } + + public string? ProjectPublishFolder + { + set => SetValue(value); + get => GetString(); + } + + public string? IconFolder + { + set => SetValue(value); + get => GetString(); + } + + public string? SvgIconFile + { + set => SetValue(value); + get => GetString(); + } + + public string? Png16x16IconFile + { + set => SetValue(value); + get => GetString(); + } + + public string? Png24x24IconFile + { + set => SetValue(value); + get => GetString(); + } + + public string? Png32x32IconFile + { + set => SetValue(value); + get => GetString(); + } + + public string? Png48x48IconFile + { + set => SetValue(value); + get => GetString(); + } + + public string? Png128x128IconFile + { + set => SetValue(value); + get => GetString(); + } + + public string? Png256x256IconFile + { + set => SetValue(value); + get => GetString(); + } + + public string? Png512x512IconFile + { + set => SetValue(value); + get => GetString(); + } } diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs new file mode 100644 index 0000000..3bec2e3 --- /dev/null +++ b/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs @@ -0,0 +1,6 @@ +namespace Packing.DebUOS; + +public class DebUOSPackageFileStructCreator +{ + +} \ No newline at end of file From 341e376a6b7228d749982671439accb8ec48ea77 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 28 Dec 2023 17:56:14 +0800 Subject: [PATCH 18/74] =?UTF-8?q?=E6=8F=90=E4=BE=9B=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs index 3bec2e3..ce2861c 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs @@ -2,5 +2,8 @@ public class DebUOSPackageFileStructCreator { + public void CreateDebUOSPackagingFolder(DebUOSConfiguration configuration) + { + } } \ No newline at end of file From 97b125a9819e2a8106203c553f77e064751e1711 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 28 Dec 2023 18:05:13 +0800 Subject: [PATCH 19/74] =?UTF-8?q?=E5=AF=B9=E6=8E=A5=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Packing.DebUOS.Tool/Build/package.targets | 205 +++++++++++++++++- DebUOS/Packing.DebUOS/DebUOSConfiguration.cs | 8 +- 2 files changed, 210 insertions(+), 3 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index 1c16dcb..25d58d2 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -10,10 +10,10 @@ - + - + @@ -27,6 +27,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs index 17b8d82..ec44374 100644 --- a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs +++ b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs @@ -14,6 +14,12 @@ public DebUOSConfiguration() : base("") { } + public string? AssemblyName + { + set => SetValue(value); + get => GetString(); + } + public string? DebControlFile { set => SetValue(value); @@ -182,7 +188,7 @@ public string? DesktopMimeType get => GetString(); } - public string PackingFolder + public string? PackingFolder { set => SetValue(value); get => GetString(); From 8e0c5f1ec5b2112938d55c9e40dbd6ae8f5c9749 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 14:23:49 +0800 Subject: [PATCH 20/74] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Options.cs | 28 +++++++++++++++++++++++++++ DebUOS/Packing.DebUOS.Tool/Program.cs | 17 ++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 DebUOS/Packing.DebUOS.Tool/Options.cs diff --git a/DebUOS/Packing.DebUOS.Tool/Options.cs b/DebUOS/Packing.DebUOS.Tool/Options.cs new file mode 100644 index 0000000..97037dc --- /dev/null +++ b/DebUOS/Packing.DebUOS.Tool/Options.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using dotnetCampus.Cli; + +namespace Packing.DebUOS.Tool; + +/// +/// 命令行参数 +/// +public class Options +{ + /// + /// 将给定路径文件夹打包为 UOS 的 deb 包 + /// + /// 和 二选一,如果同时存在,优先使用 参数 + [Option('b', "Build", Description = "Build path")] + public string? BuildPath { set; get; } + + /// + /// 将根据给定的打包参数文件打包为 UOS 的 deb 包 + /// + [Option('p', "Pack", Description = "Package argument file path")] + public string? PackageArgumentFilePath { set; get; } +} diff --git a/DebUOS/Packing.DebUOS.Tool/Program.cs b/DebUOS/Packing.DebUOS.Tool/Program.cs index 3953835..f28fa70 100644 --- a/DebUOS/Packing.DebUOS.Tool/Program.cs +++ b/DebUOS/Packing.DebUOS.Tool/Program.cs @@ -1,9 +1,26 @@ // See https://aka.ms/new-console-template for more information +using dotnetCampus.Cli; using dotnetCampus.Configurations; using dotnetCampus.Configurations.Core; using Packing.DebUOS; +using Packing.DebUOS.Tool; + +var options = CommandLine.Parse(args).As(); + +if (!string.IsNullOrEmpty(options.BuildPath)) +{ + +} +else if (!string.IsNullOrEmpty(options.PackageArgumentFilePath)) +{ + +} +else +{ + // Show Help +} //var argsFilePath = args[0]; //var fileConfigurationRepo = ConfigurationFactory.FromFile(argsFilePath,RepoSyncingBehavior.Static); From 6cd989240a404d1c9eaf3e7a37ddddcfb33bb5a1 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 14:39:32 +0800 Subject: [PATCH 21/74] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Options.cs | 3 +++ DebUOS/Packing.DebUOS.Tool/Program.cs | 12 ++++++++---- .../Properties/launchSettings.json | 8 ++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 DebUOS/Packing.DebUOS.Tool/Properties/launchSettings.json diff --git a/DebUOS/Packing.DebUOS.Tool/Options.cs b/DebUOS/Packing.DebUOS.Tool/Options.cs index 97037dc..3965ffc 100644 --- a/DebUOS/Packing.DebUOS.Tool/Options.cs +++ b/DebUOS/Packing.DebUOS.Tool/Options.cs @@ -25,4 +25,7 @@ public class Options /// [Option('p', "Pack", Description = "Package argument file path")] public string? PackageArgumentFilePath { set; get; } + + [Option('o', "Output", Description = "Output path")] + public string? OutputPath { set; get; } } diff --git a/DebUOS/Packing.DebUOS.Tool/Program.cs b/DebUOS/Packing.DebUOS.Tool/Program.cs index f28fa70..1c5dcf3 100644 --- a/DebUOS/Packing.DebUOS.Tool/Program.cs +++ b/DebUOS/Packing.DebUOS.Tool/Program.cs @@ -11,7 +11,14 @@ if (!string.IsNullOrEmpty(options.BuildPath)) { - + var packingFolder = new DirectoryInfo(options.BuildPath); + var outputPath = options.OutputPath ?? Path.Join(packingFolder.FullName,$"{packingFolder.Name}.deb"); + var outputDebFile = new FileInfo(outputPath); + + var debUosPackageCreator = new DebUOSPackageCreator(); + //var packingFolder = new DirectoryInfo(@"C:\lindexi\Work\"); + //var outputDebFile = new FileInfo(@"C:\lindexi\Work\Downloader.deb"); + debUosPackageCreator.PackageDeb(packingFolder, outputDebFile); } else if (!string.IsNullOrEmpty(options.PackageArgumentFilePath)) { @@ -26,8 +33,5 @@ //var fileConfigurationRepo = ConfigurationFactory.FromFile(argsFilePath,RepoSyncingBehavior.Static); //var appConfigurator = fileConfigurationRepo.CreateAppConfigurator(); -var debUosPackageCreator = new DebUOSPackageCreator(); -debUosPackageCreator.PackageDeb(new DirectoryInfo(@"C:\lindexi\Work\"), - new FileInfo(@"C:\lindexi\Work\Downloader.deb")); Console.Read(); \ No newline at end of file diff --git a/DebUOS/Packing.DebUOS.Tool/Properties/launchSettings.json b/DebUOS/Packing.DebUOS.Tool/Properties/launchSettings.json new file mode 100644 index 0000000..458817e --- /dev/null +++ b/DebUOS/Packing.DebUOS.Tool/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Packing.DebUOS.Tool": { + "commandName": "Project", + "commandLineArgs": "-b C:\\lindexi\\Work\\" + } + } +} \ No newline at end of file From cc13033624fb77154c2204af59ff412f088e8ed0 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 14:56:09 +0800 Subject: [PATCH 22/74] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Build/package.targets | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index 25d58d2..3f5e313 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -1,5 +1,11 @@ - + + + + + + + $([MSBuild]::NormalizePath($(IntermediateOutputPath), 'DebUOSPacking')) From 533d7526c948844ac452ee3b01b5b904260eea43 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 14:56:37 +0800 Subject: [PATCH 23/74] =?UTF-8?q?=E5=88=A0=E6=8E=89=E7=A9=BA=E5=80=BC?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Build/package.targets | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index 3f5e313..ccfccf5 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -27,11 +27,7 @@ - - - - - + From 96d20e287f3466da8a8fbddd1a7b341721b4167d Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 14:57:11 +0800 Subject: [PATCH 24/74] =?UTF-8?q?=E6=8F=90=E4=BE=9B=E6=89=93=E5=8C=85?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Packing.DebUOS.Tool/Build/package.targets | 6 +++- DebUOS/Packing.DebUOS/DebUOSConfiguration.cs | 32 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index ccfccf5..969469b 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -27,7 +27,11 @@ - + + + + + diff --git a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs index ec44374..91a46df 100644 --- a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs +++ b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -188,24 +189,55 @@ public string? DesktopMimeType get => GetString(); } + /// + /// 进行打包的文件夹,用来组织打包的文件 + /// public string? PackingFolder { set => SetValue(value); get => GetString(); } + /// + /// 工作文件夹,用来存放打包过程中的临时文件 + /// public string? WorkingFolder { set => SetValue(value); get => GetString(); } + /// + /// 项目的发布输出文件夹 + /// public string? ProjectPublishFolder { set => SetValue(value); get => GetString(); } + /// + /// 打包输出文件路径 + /// + public string? DebUOSOutputFilePath + { + set => SetValue(value); + get + { + var outputFilePath = GetString(); + if (outputFilePath is null) + { + if (!string.IsNullOrEmpty(ProjectPublishFolder)) + { + var name = AssemblyName ?? Path.GetDirectoryName(ProjectPublishFolder); + return Path.Join(ProjectPublishFolder, $"{name}.deb"); + } + } + + return outputFilePath; + } + } + public string? IconFolder { set => SetValue(value); From 08a91552b588aedf6776c2ad0f27ae016c14f37d Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 14:59:24 +0800 Subject: [PATCH 25/74] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E6=89=93=E5=8C=85=E5=9F=BA=E7=A1=80=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Program.cs | 19 ++++++++++++------- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 5 +++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Program.cs b/DebUOS/Packing.DebUOS.Tool/Program.cs index 1c5dcf3..d136971 100644 --- a/DebUOS/Packing.DebUOS.Tool/Program.cs +++ b/DebUOS/Packing.DebUOS.Tool/Program.cs @@ -22,16 +22,21 @@ } else if (!string.IsNullOrEmpty(options.PackageArgumentFilePath)) { + var fileConfigurationRepo = ConfigurationFactory.FromFile(options.PackageArgumentFilePath, RepoSyncingBehavior.Static); + var appConfigurator = fileConfigurationRepo.CreateAppConfigurator(); + var configuration = appConfigurator.Of(); + var debUosPackageCreator = new DebUOSPackageCreator(); + debUosPackageCreator.CreatePackageFolder(configuration); + + var outputFilePath = configuration.DebUOSOutputFilePath; + + var packingFolder = new DirectoryInfo(configuration.PackingFolder); + var outputDebFile = new FileInfo(outputFilePath); + + debUosPackageCreator.PackageDeb(packingFolder, outputDebFile); } else { // Show Help } - -//var argsFilePath = args[0]; -//var fileConfigurationRepo = ConfigurationFactory.FromFile(argsFilePath,RepoSyncingBehavior.Static); -//var appConfigurator = fileConfigurationRepo.CreateAppConfigurator(); - - -Console.Read(); \ No newline at end of file diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index a75fbe3..2c4a7dd 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -15,6 +15,11 @@ namespace Packing.DebUOS; public class DebUOSPackageCreator { + public void CreatePackageFolder(DebUOSConfiguration configuration) + { + + } + public void PackageDeb(DirectoryInfo packingFolder, FileInfo outputDebFile, DirectoryInfo? workingFolder = null) { ArchiveBuilder archiveBuilder = new ArchiveBuilder() From a37de4969957962c917bb42895f42b209f48f2f3 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 15:52:30 +0800 Subject: [PATCH 26/74] =?UTF-8?q?=E6=90=AD=E5=BB=BA=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Program.cs | 18 +++++++++++++++--- DebUOS/Packing.DebUOS/Packing.DebUOS.csproj | 2 ++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Program.cs b/DebUOS/Packing.DebUOS.Tool/Program.cs index d136971..513dd4a 100644 --- a/DebUOS/Packing.DebUOS.Tool/Program.cs +++ b/DebUOS/Packing.DebUOS.Tool/Program.cs @@ -3,19 +3,31 @@ using dotnetCampus.Cli; using dotnetCampus.Configurations; using dotnetCampus.Configurations.Core; - +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; using Packing.DebUOS; using Packing.DebUOS.Tool; var options = CommandLine.Parse(args).As(); +var loggerFactory = LoggerFactory.Create(builder => +{ + builder.AddSimpleConsole(simpleConsoleFormatterOptions => + { + simpleConsoleFormatterOptions.ColorBehavior = LoggerColorBehavior.Disabled; + simpleConsoleFormatterOptions.SingleLine = true; + }); +}); + +var logger = loggerFactory.CreateLogger(""); + if (!string.IsNullOrEmpty(options.BuildPath)) { var packingFolder = new DirectoryInfo(options.BuildPath); var outputPath = options.OutputPath ?? Path.Join(packingFolder.FullName,$"{packingFolder.Name}.deb"); var outputDebFile = new FileInfo(outputPath); - var debUosPackageCreator = new DebUOSPackageCreator(); + var debUosPackageCreator = new DebUOSPackageCreator(logger); //var packingFolder = new DirectoryInfo(@"C:\lindexi\Work\"); //var outputDebFile = new FileInfo(@"C:\lindexi\Work\Downloader.deb"); debUosPackageCreator.PackageDeb(packingFolder, outputDebFile); @@ -26,7 +38,7 @@ var appConfigurator = fileConfigurationRepo.CreateAppConfigurator(); var configuration = appConfigurator.Of(); - var debUosPackageCreator = new DebUOSPackageCreator(); + var debUosPackageCreator = new DebUOSPackageCreator(logger); debUosPackageCreator.CreatePackageFolder(configuration); var outputFilePath = configuration.DebUOSOutputFilePath; diff --git a/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj b/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj index 2308cd9..f959595 100644 --- a/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj +++ b/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj @@ -19,6 +19,8 @@ all runtime; build; native; contentfiles; analyzers + + From 2f855f1ce37d579d8664d4b7d97779d3ac100499 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 15:52:51 +0800 Subject: [PATCH 27/74] =?UTF-8?q?=E5=87=8F=E5=B0=91=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E7=9A=84=E5=91=BD=E4=BB=A4=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Build/package.targets | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index 969469b..5efec3e 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -1,11 +1,7 @@ - - - - - - + + $([MSBuild]::NormalizePath($(IntermediateOutputPath), 'DebUOSPacking')) @@ -153,7 +149,6 @@ - @@ -236,6 +231,6 @@ - + \ No newline at end of file From 4da77699c9fdbb9bfa6e78719af967b2da489222 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 15:53:34 +0800 Subject: [PATCH 28/74] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=E7=9A=84=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Build/package.targets | 5 +++++ DebUOS/Packing.DebUOS/DebUOSConfiguration.cs | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index 5efec3e..764c12d 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -49,6 +49,11 @@ + + + + + diff --git a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs index 91a46df..ac55e66 100644 --- a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs +++ b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs @@ -42,7 +42,13 @@ public string? DebDesktopFile public string? AppId { set => SetValue(value); - get => GetString(); + get => GetString()??AssemblyName; + } + + public string? UOSAppId + { + set => SetValue(value); + get => GetString() ?? AppId; } public string? Version @@ -230,7 +236,7 @@ public string? DebUOSOutputFilePath if (!string.IsNullOrEmpty(ProjectPublishFolder)) { var name = AssemblyName ?? Path.GetDirectoryName(ProjectPublishFolder); - return Path.Join(ProjectPublishFolder, $"{name}.deb"); + return Path.Join(ProjectPublishFolder, $"{name}.UOS.deb"); } } From c5a948e11207403c2fcb251c3abe06f534516c6b Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 15:56:17 +0800 Subject: [PATCH 29/74] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E8=BE=93=E5=87=BA=E6=96=87=E4=BB=B6=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/DebUOSConfiguration.cs | 10 ++++++++-- build/Version.props | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs index ac55e66..0272aca 100644 --- a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs +++ b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs @@ -235,8 +235,14 @@ public string? DebUOSOutputFilePath { if (!string.IsNullOrEmpty(ProjectPublishFolder)) { - var name = AssemblyName ?? Path.GetDirectoryName(ProjectPublishFolder); - return Path.Join(ProjectPublishFolder, $"{name}.UOS.deb"); + var name = UOSAppId; + + if (string.IsNullOrEmpty(name)) + { + name = Path.GetDirectoryName(ProjectPublishFolder); + } + + return Path.Join(ProjectPublishFolder, $"{name}.deb"); } } diff --git a/build/Version.props b/build/Version.props index cac44fb..1f3df31 100644 --- a/build/Version.props +++ b/build/Version.props @@ -1,5 +1,5 @@ - 1.2.1-alpha10 + 1.2.1-alpha11 \ No newline at end of file From b63c6c8ecbc7c2687988adee1ab0b420ef1ed752 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 15:56:29 +0800 Subject: [PATCH 30/74] =?UTF-8?q?=E6=90=AD=E5=BB=BA=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index 2c4a7dd..bf4c55e 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -7,14 +7,22 @@ using System.Threading.Tasks; using dotnetCampus.MSBuildUtils; - +using Microsoft.Extensions.Logging; using Packaging.Targets; using Packaging.Targets.IO; namespace Packing.DebUOS; +// ReSharper disable once InconsistentNaming public class DebUOSPackageCreator { + public DebUOSPackageCreator(ILogger logger) + { + Logger = logger; + } + + public ILogger Logger { get; } + public void CreatePackageFolder(DebUOSConfiguration configuration) { @@ -22,6 +30,8 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) public void PackageDeb(DirectoryInfo packingFolder, FileInfo outputDebFile, DirectoryInfo? workingFolder = null) { + Logger.LogInformation($"Start packing UOS deb from '{packingFolder.FullName}' to '{outputDebFile.FullName}'"); + ArchiveBuilder archiveBuilder = new ArchiveBuilder() { }; From c796f23ff9409e68d123c0e56ae0e6ac8f4e529e Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 17:49:05 +0800 Subject: [PATCH 31/74] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/DebUOSConfiguration.cs | 26 +- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 292 ++++++++++++++++++ 2 files changed, 305 insertions(+), 13 deletions(-) diff --git a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs index 0272aca..5a0a7bc 100644 --- a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs +++ b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs @@ -51,16 +51,16 @@ public string? UOSAppId get => GetString() ?? AppId; } - public string? Version + public string Version { set => SetValue(value); - get => GetString(); + get => GetString() ?? "1.0.0"; } - public string? DebControlSection + public string DebControlSection { set => SetValue(value); - get => GetString(); + get => GetString() ?? "utils"; } public string DebControlPriority @@ -99,10 +99,10 @@ public string? DebControlMaintainer get => GetString(); } - public string? DebControlHomepage + public string DebControlHomepage { set => SetValue(value); - get => GetString(); + get => GetString() ?? "https://www.uniontech.com"; } public string? DebControlDescription @@ -114,7 +114,7 @@ public string? DebControlDescription public string? AppName { set => SetValue(value); - get => GetString(); + get => GetString() ?? AssemblyName; } public string? InfoPermissions @@ -129,16 +129,16 @@ public string? AppNameZhCN get => GetString(); } - public string? DesktopCategories + public string DesktopCategories { set => SetValue(value); - get => GetString(); + get => GetString() ?? "Other"; } - public string? DesktopKeywords + public string DesktopKeywords { set => SetValue(value); - get => GetString(); + get => GetString()?? "deepin"; } public string? DesktopKeywordsZhCN @@ -147,10 +147,10 @@ public string? DesktopKeywordsZhCN get => GetString(); } - public string? DesktopComment + public string DesktopComment { set => SetValue(value); - get => GetString(); + get => GetString() ?? UOSAppId; } public string? DesktopCommentZhCN diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index bf4c55e..bf12896 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -4,10 +4,15 @@ using System.IO.Compression; using System.Linq; using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; using System.Threading.Tasks; using dotnetCampus.MSBuildUtils; + using Microsoft.Extensions.Logging; + using Packaging.Targets; using Packaging.Targets.IO; @@ -25,7 +30,251 @@ public DebUOSPackageCreator(ILogger logger) public void CreatePackageFolder(DebUOSConfiguration configuration) { + var projectPublishFolder = configuration.ProjectPublishFolder; + if (!Directory.Exists(projectPublishFolder)) + { + Logger.LogError($"Project publish folder '{projectPublishFolder}' not exist"); + return; + } + + var workingFolder = configuration.WorkingFolder; + if (string.IsNullOrEmpty(workingFolder)) + { + workingFolder = Path.Join(Path.GetTempPath(), "DebUOSPacking", + $"{configuration.AssemblyName}_{Path.GetRandomFileName()}"); + } + + Directory.CreateDirectory(workingFolder); + + var packingFolder = configuration.PackingFolder; + if (string.IsNullOrEmpty(packingFolder)) + { + packingFolder = Path.Join(workingFolder, "Packing"); + } + // 删除旧的文件夹,防止打包使用到旧文件 + Directory.Delete(packingFolder, true); + Directory.CreateDirectory(packingFolder); + var appId = configuration.UOSAppId; + if (string.IsNullOrEmpty(appId)) + { + Logger.LogError($"找不到 UOS 的 AppId 内容,请确保已经配置 UOSAppId 属性"); + return; + } + + var match = Regex.Match(appId, @"[a-z\.]+"); + if (!match.Success || match.Value != appId) + { + Logger.LogError($"UOS 的 AppId 内容不符合规范,请确保配置的 UOSAppId 属性符合规范。请务必使用厂商的倒置域名+产品名作为应用包名,如 `com.example.demo` 格式,且只允许使用小写字母"); + return; + } + + // opt\apps\AppId\ + // opt\apps\AppId\files + var appIdFolder = Path.Join(packingFolder, "opt", "apps", appId); + var filesFolder = Path.Join(appIdFolder, "files"); + Directory.CreateDirectory(filesFolder); + var applicationBin = Path.Join(filesFolder, "bin"); + // 符号比拷贝速度快 + var symbol = Directory.CreateSymbolicLink(applicationBin, projectPublishFolder); + if (!symbol.Exists) + { + Logger.LogError($"创建符号链接失败,从 '{projectPublishFolder}' 到 '{applicationBin}'"); + return; + } + + // opt\apps\AppId\entries + // opt\apps\AppId\entries\applications + var entriesFolder = Path.Join(appIdFolder, "entries"); + var applicationsFolder = Path.Join(entriesFolder, "applications"); + var desktopFile = Path.Join(applicationsFolder, $"{appId}.desktop"); + + if (File.Exists(configuration.DebDesktopFile)) + { + // 开发者配置了自定义的文件,则使用开发者的文件 + File.Copy(configuration.DebDesktopFile, desktopFile); + } + else + { + var stringBuilder = new StringBuilder(); + // 这里不能使用 AppendLine 方法,保持换行使用 \n 字符 + stringBuilder + .Append("[Desktop Entry]\n") + .Append($"Categories={configuration.DesktopCategories}\n") + .Append($"Name={configuration.AppName}\n") + .Append($"Keywords={configuration.DesktopKeywords}\n") + .Append($"Comment={configuration.DesktopComment}\n") + .Append($"Type={configuration.DesktopType}\n") + .Append($"Terminal={configuration.DesktopTerminal.ToString().ToLowerInvariant()}\n") + .Append($"StartupNotify={configuration.DesktopStartupNotify.ToString().ToLowerInvariant()}\n"); + + if (!string.IsNullOrEmpty(configuration.AppNameZhCN)) + { + stringBuilder.Append($"Name[zh_CN]={configuration.AppNameZhCN}\n"); + } + + if (!string.IsNullOrEmpty(configuration.DesktopKeywordsZhCN)) + { + stringBuilder.Append($"Keywords[zh_CN]={configuration.DesktopKeywordsZhCN}\n"); + } + + if (!string.IsNullOrEmpty(configuration.DesktopCommentZhCN)) + { + stringBuilder.Append($"Comment[zh_CN]={configuration.DesktopCommentZhCN}\n"); + } + + if (!string.IsNullOrEmpty(configuration.DesktopExec)) + { + stringBuilder.Append($"Exec={configuration.DesktopExec}\n"); + } + else + { + // 这里不能使用 Path.Join 方法,因为如果在 Windows 上进行打包,会将 \ 替换为 /,导致打包失败 + //var exec = Path.Join("/opt/apps", appId, "files", "bin", configuration.AssemblyName); + var exec = $"/opt/apps/{appId}/files/bin/{configuration.AssemblyName}"; + stringBuilder.Append($"Exec={exec}\n"); + } + + if (!string.IsNullOrEmpty(configuration.DesktopIcon)) + { + stringBuilder.Append($"Icon={configuration.DesktopIcon}\n"); + } + else + { + stringBuilder.Append($"Icon={appId}\n"); + } + + if (!string.IsNullOrEmpty(configuration.DesktopMimeType)) + { + stringBuilder.Append($"MimeType={configuration.DesktopMimeType}\n"); + } + + File.WriteAllText(desktopFile, stringBuilder.ToString(), Encoding.UTF8); + } + + // opt\apps\AppId\entries\icons + if (File.Exists(configuration.SvgIconFile)) + { + var svgFile = Path.Join(entriesFolder, "icons", "hicolor", "scalable", "apps", $"{appId}.svg"); + Directory.CreateDirectory(Path.GetDirectoryName(svgFile)!); + File.Copy(configuration.SvgIconFile, svgFile); + } + else + { + bool anyIconFileExist = false; + foreach (var (iconFile, resolution) in new (string? iconFile, string resolution)[] + { + (configuration.Png16x16IconFile, "16x16"), + (configuration.Png24x24IconFile, "24x24"), + (configuration.Png32x32IconFile, "32x32"), + (configuration.Png48x48IconFile, "48x48"), + (configuration.Png128x128IconFile, "128x128"), + (configuration.Png256x256IconFile, "256x256"), + (configuration.Png512x512IconFile,"512x512"), + }) + { + if (File.Exists(iconFile)) + { + var pngFile = Path.Join(entriesFolder, "icons", "hicolor", resolution, "apps", $"{appId}.png"); + Directory.CreateDirectory(Path.GetDirectoryName(pngFile)!); + File.Copy(iconFile, pngFile); + + anyIconFileExist = true; + } + } + + if (!anyIconFileExist) + { + Logger.LogWarning("找不到任何的图标文件。可通过 SvgIconFile 配置矢量图,可通过 Png16x16IconFile 等属性配置不同分辨率的图标"); + } + } + + // opt\apps\AppId\info + var infoJsonFile = Path.Join(appIdFolder, "info"); + if (File.Exists(configuration.DebInfoFile)) + { + File.Copy(configuration.DebInfoFile, infoJsonFile); + } + else + { + var data = new ApplicationInfoFileData() + { + AppId = appId, + ApplicationName = configuration.AppName, + Version = configuration.Version, + Architecture = configuration.Architecture.Split(';') + }; + + var permissions = configuration.InfoPermissions?.Split(';'); + if (permissions != null && permissions.Length > 0) + { + var applicationInfoPermissions = new ApplicationInfoPermissions(); + foreach (var permission in permissions) + { + switch (permission) + { + case "autostart": + applicationInfoPermissions.Autostart = true; + break; + case "notification": + applicationInfoPermissions.Notification = true; + break; + case "trayicon": + applicationInfoPermissions.TrayIcon = true; + break; + case "clipboard": + applicationInfoPermissions.Clipboard = true; + break; + case "account": + applicationInfoPermissions.Account = true; + break; + case "bluetooth": + applicationInfoPermissions.Bluetooth = true; + break; + case "camera": + applicationInfoPermissions.Camera = true; + break; + case "audio_record": + applicationInfoPermissions.AudioRecord = true; + break; + case "installed_apps": + applicationInfoPermissions.InstalledApps = true; + break; + } + } + data.Permissions = applicationInfoPermissions; + } + + var json = JsonSerializer.Serialize(data); + File.WriteAllText(infoJsonFile, json, Encoding.UTF8); + } + + // 创建 control 文件 + var controlFile = Path.Join(packingFolder, "DEBIAN", "control"); + if (File.Exists(configuration.DebControlFile)) + { + File.Copy(configuration.DebControlFile, controlFile); + } + else + { + var stringBuilder = new StringBuilder(); + stringBuilder + .Append($"Package: {appId}\n") + .Append($"Version: {configuration.Version}\n") + .Append($"Section: {configuration.DebControlSection}\n") + .Append($"Priority: {configuration.DebControlPriority}\n") + .Append($"Architecture: {configuration.Architecture}\n") + .Append($"Multi-Arch: {configuration.DebControlMultiArch}\n") + .Append($"Build-Depends: {configuration.DebControlBuildDepends}\n") + .Append($"Standards-Version: {configuration.DebControlStandardsVersion}\n") + .Append($"Maintainer: {configuration.DebControlMaintainer}\n") + .Append($"Homepage: {configuration.DebControlHomepage}") + .Append($"Description: {configuration.DebControlDescription}\n") + // 必须添加最后一行,否则 missing final newline 失败 + .Append("\n") + ; + File.WriteAllText(controlFile, stringBuilder.ToString(), Encoding.UTF8); + } } public void PackageDeb(DirectoryInfo packingFolder, FileInfo outputDebFile, DirectoryInfo? workingFolder = null) @@ -195,3 +444,46 @@ void EnsureDir(string dirPath) private const LinuxFileMode ArFileMode = LinuxFileMode.S_IRUSR | LinuxFileMode.S_IWUSR | LinuxFileMode.S_IRGRP | LinuxFileMode.S_IROTH | LinuxFileMode.S_IFREG; } + +class ApplicationInfoFileData +{ + [JsonPropertyName("appid")] + public string? AppId { init; get; } + + [JsonPropertyName("name")] + public string? ApplicationName { init; get; } + + [JsonPropertyName("version")] + public string? Version { init; get; } + + [JsonPropertyName("arch")] + public IList? Architecture { init; get; } + + [JsonPropertyName("permissions")] + public ApplicationInfoPermissions? Permissions { set; get; } +} + +class ApplicationInfoPermissions +{ + [JsonPropertyName("autostart")] + public bool Autostart { get; set; } + [JsonPropertyName("notification")] + public bool Notification { get; set; } + [JsonPropertyName("trayicon")] + public bool TrayIcon { get; set; } + [JsonPropertyName("clipboard")] + public bool Clipboard { get; set; } + [JsonPropertyName("account")] + public bool Account { get; set; } + [JsonPropertyName("bluetooth")] + public bool Bluetooth { get; set; } + [JsonPropertyName("camera")] + public bool Camera { get; set; } + [JsonPropertyName("audio_record")] + public bool AudioRecord { get; set; } + [JsonPropertyName("installed_apps")] + public bool InstalledApps { get; set; } +} + + + From ce53d504198dba12f0a1543c8b4267c308412f2d Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 19:29:52 +0800 Subject: [PATCH 32/74] =?UTF-8?q?=E5=8F=AA=E6=9C=89=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E6=97=A7=E7=9A=84=E6=96=87=E4=BB=B6=E5=A4=B9=E6=89=8D=E8=83=BD?= =?UTF-8?q?=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Properties/launchSettings.json | 2 +- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Properties/launchSettings.json b/DebUOS/Packing.DebUOS.Tool/Properties/launchSettings.json index 458817e..47e7f7c 100644 --- a/DebUOS/Packing.DebUOS.Tool/Properties/launchSettings.json +++ b/DebUOS/Packing.DebUOS.Tool/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Packing.DebUOS.Tool": { "commandName": "Project", - "commandLineArgs": "-b C:\\lindexi\\Work\\" + "commandLineArgs": "-p C:\\lindexi\\Work\\DebUOSPackingArgs.coin" } } } \ No newline at end of file diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index bf12896..5614f75 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -52,7 +52,11 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) packingFolder = Path.Join(workingFolder, "Packing"); } // 删除旧的文件夹,防止打包使用到旧文件 - Directory.Delete(packingFolder, true); + if(Directory.Exists(packingFolder)) + { + Directory.Delete(packingFolder, true); + } + Directory.CreateDirectory(packingFolder); var appId = configuration.UOSAppId; From c9f78373efcda711aef9220bb0660a1d4148ce9a Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 19:34:11 +0800 Subject: [PATCH 33/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=86=99=E5=85=A5?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index 5614f75..1d74a41 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -92,6 +92,7 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) var entriesFolder = Path.Join(appIdFolder, "entries"); var applicationsFolder = Path.Join(entriesFolder, "applications"); var desktopFile = Path.Join(applicationsFolder, $"{appId}.desktop"); + Directory.CreateDirectory(applicationsFolder); if (File.Exists(configuration.DebDesktopFile)) { @@ -255,6 +256,7 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) // 创建 control 文件 var controlFile = Path.Join(packingFolder, "DEBIAN", "control"); + Directory.CreateDirectory(Path.GetDirectoryName(controlFile)!); if (File.Exists(configuration.DebControlFile)) { File.Copy(configuration.DebControlFile, controlFile); From 2f633ec46b02ab38a87126377d026055110fbe5c Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 19:53:31 +0800 Subject: [PATCH 34/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index 1d74a41..b6657fb 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -250,7 +250,10 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) data.Permissions = applicationInfoPermissions; } - var json = JsonSerializer.Serialize(data); + var json = JsonSerializer.Serialize(data,new JsonSerializerOptions() + { + WriteIndented = true, + }).Replace("\r\n","\n"); File.WriteAllText(infoJsonFile, json, Encoding.UTF8); } From c8eb566b4adf67033d1c73351a51f2f905479652 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 19:55:01 +0800 Subject: [PATCH 35/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8D=A2=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index b6657fb..605d683 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -277,10 +277,8 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) .Append($"Build-Depends: {configuration.DebControlBuildDepends}\n") .Append($"Standards-Version: {configuration.DebControlStandardsVersion}\n") .Append($"Maintainer: {configuration.DebControlMaintainer}\n") - .Append($"Homepage: {configuration.DebControlHomepage}") + .Append($"Homepage: {configuration.DebControlHomepage}\n") .Append($"Description: {configuration.DebControlDescription}\n") - // 必须添加最后一行,否则 missing final newline 失败 - .Append("\n") ; File.WriteAllText(controlFile, stringBuilder.ToString(), Encoding.UTF8); } From 2bec41250b00fe057c7e7eab5d36edeed4013093 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 20:03:43 +0800 Subject: [PATCH 36/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=89=93=E5=8C=85?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index 605d683..ba8cdfc 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -45,6 +45,7 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) } Directory.CreateDirectory(workingFolder); + configuration.WorkingFolder = workingFolder; var packingFolder = configuration.PackingFolder; if (string.IsNullOrEmpty(packingFolder)) @@ -58,6 +59,7 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) } Directory.CreateDirectory(packingFolder); + configuration.PackingFolder = packingFolder; var appId = configuration.UOSAppId; if (string.IsNullOrEmpty(appId)) @@ -94,6 +96,8 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) var desktopFile = Path.Join(applicationsFolder, $"{appId}.desktop"); Directory.CreateDirectory(applicationsFolder); + var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + if (File.Exists(configuration.DebDesktopFile)) { // 开发者配置了自定义的文件,则使用开发者的文件 @@ -154,7 +158,7 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) stringBuilder.Append($"MimeType={configuration.DesktopMimeType}\n"); } - File.WriteAllText(desktopFile, stringBuilder.ToString(), Encoding.UTF8); + File.WriteAllText(desktopFile, stringBuilder.ToString(), encoding); } // opt\apps\AppId\entries\icons @@ -254,7 +258,7 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) { WriteIndented = true, }).Replace("\r\n","\n"); - File.WriteAllText(infoJsonFile, json, Encoding.UTF8); + File.WriteAllText(infoJsonFile, json, encoding); } // 创建 control 文件 @@ -276,11 +280,27 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) .Append($"Multi-Arch: {configuration.DebControlMultiArch}\n") .Append($"Build-Depends: {configuration.DebControlBuildDepends}\n") .Append($"Standards-Version: {configuration.DebControlStandardsVersion}\n") - .Append($"Maintainer: {configuration.DebControlMaintainer}\n") .Append($"Homepage: {configuration.DebControlHomepage}\n") - .Append($"Description: {configuration.DebControlDescription}\n") ; - File.WriteAllText(controlFile, stringBuilder.ToString(), Encoding.UTF8); + if (!string.IsNullOrEmpty(configuration.DebControlMaintainer)) + { + stringBuilder.Append($"Maintainer: {configuration.DebControlMaintainer}\n"); + } + else + { + Logger.LogWarning($"没有找到 DebControlMaintainer 属性配置。请配置 deb 包的维护者,如 lindexi 格式"); + } + + if (!string.IsNullOrEmpty(configuration.DebControlDescription)) + { + stringBuilder.Append($"Description: {configuration.DebControlDescription}\n"); + } + else + { + Logger.LogWarning($"没有找到 DebControlDescription 属性配置。请配置 deb 包的描述,描述可使用中文"); + } + + File.WriteAllText(controlFile, stringBuilder.ToString(), encoding); } } From 44eae2865b5937566566e560d1d80be49ddab8b5 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 20:11:00 +0800 Subject: [PATCH 37/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Program.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Program.cs b/DebUOS/Packing.DebUOS.Tool/Program.cs index 513dd4a..7b84d1d 100644 --- a/DebUOS/Packing.DebUOS.Tool/Program.cs +++ b/DebUOS/Packing.DebUOS.Tool/Program.cs @@ -41,12 +41,11 @@ var debUosPackageCreator = new DebUOSPackageCreator(logger); debUosPackageCreator.CreatePackageFolder(configuration); - var outputFilePath = configuration.DebUOSOutputFilePath; + var packingFolder = new DirectoryInfo(configuration.PackingFolder!); + var outputDebFile = new FileInfo(configuration.DebUOSOutputFilePath!); + var workingFolder = new DirectoryInfo(configuration.WorkingFolder!); - var packingFolder = new DirectoryInfo(configuration.PackingFolder); - var outputDebFile = new FileInfo(outputFilePath); - - debUosPackageCreator.PackageDeb(packingFolder, outputDebFile); + debUosPackageCreator.PackageDeb(packingFolder, outputDebFile,workingFolder); } else { From ddf09f15b389a52dc64d2bdb9c6312074af83c7f Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 20:27:58 +0800 Subject: [PATCH 38/74] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AD=A6=E5=91=8A?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index ba8cdfc..c18e440 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -7,10 +7,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; -using System.Threading.Tasks; - -using dotnetCampus.MSBuildUtils; - using Microsoft.Extensions.Logging; using Packaging.Targets; @@ -288,7 +284,7 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) } else { - Logger.LogWarning($"没有找到 DebControlMaintainer 属性配置。请配置 deb 包的维护者,如 lindexi 格式"); + Logger.LogWarning($"没有找到 DebControlMaintainer 属性配置。安装完成可能在开始菜单找不到应用。请配置 deb 包的维护者,如 lindexi 格式"); } if (!string.IsNullOrEmpty(configuration.DebControlDescription)) @@ -297,7 +293,7 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) } else { - Logger.LogWarning($"没有找到 DebControlDescription 属性配置。请配置 deb 包的描述,描述可使用中文"); + Logger.LogWarning($"没有找到 DebControlDescription 属性配置。安装完成可能在开始菜单找不到应用。请配置 deb 包的描述,描述可使用中文"); } File.WriteAllText(controlFile, stringBuilder.ToString(), encoding); From 6c3110d4a8681b560241fc3c4009505d4e841584 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 20:35:55 +0800 Subject: [PATCH 39/74] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=8F=82=E6=95=B0=EF=BC=8C=E8=AE=A9=E6=89=93?= =?UTF-8?q?=E5=8C=85=E6=9B=B4=E5=8A=A0=E6=96=B9=E4=BE=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Packing.DebUOS.Tool/Build/package.targets | 57 +++++++++++-------- DebUOS/Packing.DebUOS/DebUOSConfiguration.cs | 3 +- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index 764c12d..50eafc9 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -8,6 +8,15 @@ $([MSBuild]::NormalizePath($(DebUOSPackingWorkFolder), 'DebUOSPackingArgs.coin')) + + + $(Product) + $(Description) + $(Authors) + $(Author) + $(Company) + $(Publisher) + $(PackageProjectUrl) @@ -25,122 +34,122 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs index 5a0a7bc..50d06a3 100644 --- a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs +++ b/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs @@ -1,4 +1,5 @@ -using dotnetCampus.Configurations; +// ReSharper disable InconsistentNaming +using dotnetCampus.Configurations; using System; using System.Collections.Generic; From 9906ae4a0851d309febcff878f0184c8d4e437b5 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 20:39:57 +0800 Subject: [PATCH 40/74] =?UTF-8?q?=E5=87=86=E5=A4=87=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 2 +- build/Version.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index c18e440..70f3545 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -190,7 +190,7 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) if (!anyIconFileExist) { - Logger.LogWarning("找不到任何的图标文件。可通过 SvgIconFile 配置矢量图,可通过 Png16x16IconFile 等属性配置不同分辨率的图标"); + Logger.LogWarning("找不到任何的图标文件,将导致应用无法在开始菜单显示。可通过 SvgIconFile 配置矢量图,可通过 Png16x16IconFile 等属性配置不同分辨率的图标"); } } diff --git a/build/Version.props b/build/Version.props index 1f3df31..eb51cd5 100644 --- a/build/Version.props +++ b/build/Version.props @@ -1,5 +1,5 @@ - 1.2.1-alpha11 + 1.2.1-alpha12 \ No newline at end of file From 7fd3b5c44874edc8b3fea995be087014e80b76a3 Mon Sep 17 00:00:00 2001 From: lindexi Date: Tue, 2 Jan 2024 20:54:18 +0800 Subject: [PATCH 41/74] =?UTF-8?q?=E5=87=86=E5=A4=87=E5=AF=B9=E6=8E=A5?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Build/package.targets | 2 +- DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj | 1 + build/Version.props | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packing.DebUOS.Tool/Build/package.targets index 50eafc9..1f4c5d9 100644 --- a/DebUOS/Packing.DebUOS.Tool/Build/package.targets +++ b/DebUOS/Packing.DebUOS.Tool/Build/package.targets @@ -245,6 +245,6 @@ - + \ No newline at end of file diff --git a/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj b/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj index 4247f9d..f2eae6a 100644 --- a/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj +++ b/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj @@ -23,6 +23,7 @@ + diff --git a/build/Version.props b/build/Version.props index eb51cd5..b8983a1 100644 --- a/build/Version.props +++ b/build/Version.props @@ -1,5 +1,5 @@ - 1.2.1-alpha12 + 1.2.1-alpha16 \ No newline at end of file From ed43d593027a547ab601ad24577d0876e1cd745d Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 09:27:51 +0800 Subject: [PATCH 42/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9B=A0=E4=B8=BA?= =?UTF-8?q?=E7=9B=B8=E5=AF=B9=E8=B7=AF=E5=BE=84=E9=94=99=E8=AF=AF=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=89=BE=E4=B8=8D=E5=88=B0=E5=8A=A0=E8=BD=BD=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.Targets/IO/NativeMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DebUOS/Packaging.Targets/IO/NativeMethods.cs b/DebUOS/Packaging.Targets/IO/NativeMethods.cs index 9b73f34..4ede7c2 100644 --- a/DebUOS/Packaging.Targets/IO/NativeMethods.cs +++ b/DebUOS/Packaging.Targets/IO/NativeMethods.cs @@ -50,7 +50,7 @@ static NativeMethods() var lzmaWindowsPath = Path.GetFullPath(Path.Combine(libraryPath, "../../runtimes/win7-x64/native/lzma.dll")); IntPtr library = FunctionLoader.LoadNativeLibrary( - new string[] { lzmaWindowsPath, "lzma.dll" }, // lzma.dll is used when running unit tests. + new string[] { lzmaWindowsPath, Path.GetFullPath(Path.Combine(libraryPath, "lzma.dll")) }, // lzma.dll is used when running unit tests. new string[] { "liblzma.so.5", "liblzma.so" }, new string[] { "liblzma.dylib" }); From c491f09c26cfc2ba21fdb2e5676cbf32fbc2b8de Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 09:30:41 +0800 Subject: [PATCH 43/74] =?UTF-8?q?=E5=87=86=E5=A4=87=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contexts/ApplicationInfoFileData.cs | 22 +++++++++ .../Contexts/ApplicationInfoPermissions.cs | 25 ++++++++++ DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 48 ++----------------- 3 files changed, 50 insertions(+), 45 deletions(-) create mode 100644 DebUOS/Packing.DebUOS/Contexts/ApplicationInfoFileData.cs create mode 100644 DebUOS/Packing.DebUOS/Contexts/ApplicationInfoPermissions.cs diff --git a/DebUOS/Packing.DebUOS/Contexts/ApplicationInfoFileData.cs b/DebUOS/Packing.DebUOS/Contexts/ApplicationInfoFileData.cs new file mode 100644 index 0000000..3675421 --- /dev/null +++ b/DebUOS/Packing.DebUOS/Contexts/ApplicationInfoFileData.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Packing.DebUOS.Contexts; + +class ApplicationInfoFileData +{ + [JsonPropertyName("appid")] + public string? AppId { init; get; } + + [JsonPropertyName("name")] + public string? ApplicationName { init; get; } + + [JsonPropertyName("version")] + public string? Version { init; get; } + + [JsonPropertyName("arch")] + public IList? Architecture { init; get; } + + [JsonPropertyName("permissions")] + public ApplicationInfoPermissions? Permissions { set; get; } +} \ No newline at end of file diff --git a/DebUOS/Packing.DebUOS/Contexts/ApplicationInfoPermissions.cs b/DebUOS/Packing.DebUOS/Contexts/ApplicationInfoPermissions.cs new file mode 100644 index 0000000..8b93907 --- /dev/null +++ b/DebUOS/Packing.DebUOS/Contexts/ApplicationInfoPermissions.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; + +namespace Packing.DebUOS.Contexts; + +class ApplicationInfoPermissions +{ + [JsonPropertyName("autostart")] + public bool Autostart { get; set; } + [JsonPropertyName("notification")] + public bool Notification { get; set; } + [JsonPropertyName("trayicon")] + public bool TrayIcon { get; set; } + [JsonPropertyName("clipboard")] + public bool Clipboard { get; set; } + [JsonPropertyName("account")] + public bool Account { get; set; } + [JsonPropertyName("bluetooth")] + public bool Bluetooth { get; set; } + [JsonPropertyName("camera")] + public bool Camera { get; set; } + [JsonPropertyName("audio_record")] + public bool AudioRecord { get; set; } + [JsonPropertyName("installed_apps")] + public bool InstalledApps { get; set; } +} \ No newline at end of file diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index 70f3545..fe4dafe 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -5,12 +5,12 @@ using System.Linq; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using Packaging.Targets; using Packaging.Targets.IO; +using Packing.DebUOS.Contexts; namespace Packing.DebUOS; @@ -92,6 +92,7 @@ public void CreatePackageFolder(DebUOSConfiguration configuration) var desktopFile = Path.Join(applicationsFolder, $"{appId}.desktop"); Directory.CreateDirectory(applicationsFolder); + // 不能使用 Encoding.UTF8 编码,因为默认会写入 BOM 导致 deb 打包失败 var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); if (File.Exists(configuration.DebDesktopFile)) @@ -466,47 +467,4 @@ void EnsureDir(string dirPath) private const LinuxFileMode ArFileMode = LinuxFileMode.S_IRUSR | LinuxFileMode.S_IWUSR | LinuxFileMode.S_IRGRP | LinuxFileMode.S_IROTH | LinuxFileMode.S_IFREG; -} - -class ApplicationInfoFileData -{ - [JsonPropertyName("appid")] - public string? AppId { init; get; } - - [JsonPropertyName("name")] - public string? ApplicationName { init; get; } - - [JsonPropertyName("version")] - public string? Version { init; get; } - - [JsonPropertyName("arch")] - public IList? Architecture { init; get; } - - [JsonPropertyName("permissions")] - public ApplicationInfoPermissions? Permissions { set; get; } -} - -class ApplicationInfoPermissions -{ - [JsonPropertyName("autostart")] - public bool Autostart { get; set; } - [JsonPropertyName("notification")] - public bool Notification { get; set; } - [JsonPropertyName("trayicon")] - public bool TrayIcon { get; set; } - [JsonPropertyName("clipboard")] - public bool Clipboard { get; set; } - [JsonPropertyName("account")] - public bool Account { get; set; } - [JsonPropertyName("bluetooth")] - public bool Bluetooth { get; set; } - [JsonPropertyName("camera")] - public bool Camera { get; set; } - [JsonPropertyName("audio_record")] - public bool AudioRecord { get; set; } - [JsonPropertyName("installed_apps")] - public bool InstalledApps { get; set; } -} - - - +} \ No newline at end of file From a4d0e40580babe7a0b66868b162b1e498977fe3e Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 09:32:59 +0800 Subject: [PATCH 44/74] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS/Contexts/ApplicationInfoFileData.cs | 4 ++++ DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/DebUOS/Packing.DebUOS/Contexts/ApplicationInfoFileData.cs b/DebUOS/Packing.DebUOS/Contexts/ApplicationInfoFileData.cs index 3675421..09ec21c 100644 --- a/DebUOS/Packing.DebUOS/Contexts/ApplicationInfoFileData.cs +++ b/DebUOS/Packing.DebUOS/Contexts/ApplicationInfoFileData.cs @@ -3,6 +3,10 @@ namespace Packing.DebUOS.Contexts; +/// +/// 用于写入到 opt\apps\${AppId}\info 文件的数据内容 +/// +/// 将使用 json 格式写入 class ApplicationInfoFileData { [JsonPropertyName("appid")] diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index fe4dafe..6e641e0 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -14,6 +14,10 @@ namespace Packing.DebUOS; +/// +/// 打出 UOS 的 deb 包 +/// +/// 打包细节请参阅 [一步步教你在 Windows 上构建 dotnet 系应用的 UOS 软件安装包](https://blog.lindexi.com/post/%E4%B8%80%E6%AD%A5%E6%AD%A5%E6%95%99%E4%BD%A0%E5%9C%A8-Windows-%E4%B8%8A%E6%9E%84%E5%BB%BA-dotnet-%E7%B3%BB%E5%BA%94%E7%94%A8%E7%9A%84-UOS-%E8%BD%AF%E4%BB%B6%E5%AE%89%E8%A3%85%E5%8C%85.html ) // ReSharper disable once InconsistentNaming public class DebUOSPackageCreator { From 84f6b049626257bc49e7f0569a730f63fe6dbf0b Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 09:35:37 +0800 Subject: [PATCH 45/74] =?UTF-8?q?=E6=8B=86=E5=88=86=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=AE=9E=E7=8E=B0=E4=B8=8D=E5=90=8C=E7=9A=84?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 减少单个类包含太多逻辑 --- DebUOS/Packing.DebUOS.Tool/Program.cs | 5 +- DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs | 277 ---------------- .../DebUOSPackageFileStructCreator.cs | 300 +++++++++++++++++- 3 files changed, 301 insertions(+), 281 deletions(-) diff --git a/DebUOS/Packing.DebUOS.Tool/Program.cs b/DebUOS/Packing.DebUOS.Tool/Program.cs index 7b84d1d..0961c0d 100644 --- a/DebUOS/Packing.DebUOS.Tool/Program.cs +++ b/DebUOS/Packing.DebUOS.Tool/Program.cs @@ -38,13 +38,14 @@ var appConfigurator = fileConfigurationRepo.CreateAppConfigurator(); var configuration = appConfigurator.Of(); - var debUosPackageCreator = new DebUOSPackageCreator(logger); - debUosPackageCreator.CreatePackageFolder(configuration); + var fileStructCreator = new DebUOSPackageFileStructCreator(logger); + fileStructCreator.CreatePackagingFolder(configuration); var packingFolder = new DirectoryInfo(configuration.PackingFolder!); var outputDebFile = new FileInfo(configuration.DebUOSOutputFilePath!); var workingFolder = new DirectoryInfo(configuration.WorkingFolder!); + var debUosPackageCreator = new DebUOSPackageCreator(logger); debUosPackageCreator.PackageDeb(packingFolder, outputDebFile,workingFolder); } else diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs index 6e641e0..770db3c 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs @@ -28,283 +28,6 @@ public DebUOSPackageCreator(ILogger logger) public ILogger Logger { get; } - public void CreatePackageFolder(DebUOSConfiguration configuration) - { - var projectPublishFolder = configuration.ProjectPublishFolder; - if (!Directory.Exists(projectPublishFolder)) - { - Logger.LogError($"Project publish folder '{projectPublishFolder}' not exist"); - return; - } - - var workingFolder = configuration.WorkingFolder; - if (string.IsNullOrEmpty(workingFolder)) - { - workingFolder = Path.Join(Path.GetTempPath(), "DebUOSPacking", - $"{configuration.AssemblyName}_{Path.GetRandomFileName()}"); - } - - Directory.CreateDirectory(workingFolder); - configuration.WorkingFolder = workingFolder; - - var packingFolder = configuration.PackingFolder; - if (string.IsNullOrEmpty(packingFolder)) - { - packingFolder = Path.Join(workingFolder, "Packing"); - } - // 删除旧的文件夹,防止打包使用到旧文件 - if(Directory.Exists(packingFolder)) - { - Directory.Delete(packingFolder, true); - } - - Directory.CreateDirectory(packingFolder); - configuration.PackingFolder = packingFolder; - - var appId = configuration.UOSAppId; - if (string.IsNullOrEmpty(appId)) - { - Logger.LogError($"找不到 UOS 的 AppId 内容,请确保已经配置 UOSAppId 属性"); - return; - } - - var match = Regex.Match(appId, @"[a-z\.]+"); - if (!match.Success || match.Value != appId) - { - Logger.LogError($"UOS 的 AppId 内容不符合规范,请确保配置的 UOSAppId 属性符合规范。请务必使用厂商的倒置域名+产品名作为应用包名,如 `com.example.demo` 格式,且只允许使用小写字母"); - return; - } - - // opt\apps\AppId\ - // opt\apps\AppId\files - var appIdFolder = Path.Join(packingFolder, "opt", "apps", appId); - var filesFolder = Path.Join(appIdFolder, "files"); - Directory.CreateDirectory(filesFolder); - var applicationBin = Path.Join(filesFolder, "bin"); - // 符号比拷贝速度快 - var symbol = Directory.CreateSymbolicLink(applicationBin, projectPublishFolder); - if (!symbol.Exists) - { - Logger.LogError($"创建符号链接失败,从 '{projectPublishFolder}' 到 '{applicationBin}'"); - return; - } - - // opt\apps\AppId\entries - // opt\apps\AppId\entries\applications - var entriesFolder = Path.Join(appIdFolder, "entries"); - var applicationsFolder = Path.Join(entriesFolder, "applications"); - var desktopFile = Path.Join(applicationsFolder, $"{appId}.desktop"); - Directory.CreateDirectory(applicationsFolder); - - // 不能使用 Encoding.UTF8 编码,因为默认会写入 BOM 导致 deb 打包失败 - var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - - if (File.Exists(configuration.DebDesktopFile)) - { - // 开发者配置了自定义的文件,则使用开发者的文件 - File.Copy(configuration.DebDesktopFile, desktopFile); - } - else - { - var stringBuilder = new StringBuilder(); - // 这里不能使用 AppendLine 方法,保持换行使用 \n 字符 - stringBuilder - .Append("[Desktop Entry]\n") - .Append($"Categories={configuration.DesktopCategories}\n") - .Append($"Name={configuration.AppName}\n") - .Append($"Keywords={configuration.DesktopKeywords}\n") - .Append($"Comment={configuration.DesktopComment}\n") - .Append($"Type={configuration.DesktopType}\n") - .Append($"Terminal={configuration.DesktopTerminal.ToString().ToLowerInvariant()}\n") - .Append($"StartupNotify={configuration.DesktopStartupNotify.ToString().ToLowerInvariant()}\n"); - - if (!string.IsNullOrEmpty(configuration.AppNameZhCN)) - { - stringBuilder.Append($"Name[zh_CN]={configuration.AppNameZhCN}\n"); - } - - if (!string.IsNullOrEmpty(configuration.DesktopKeywordsZhCN)) - { - stringBuilder.Append($"Keywords[zh_CN]={configuration.DesktopKeywordsZhCN}\n"); - } - - if (!string.IsNullOrEmpty(configuration.DesktopCommentZhCN)) - { - stringBuilder.Append($"Comment[zh_CN]={configuration.DesktopCommentZhCN}\n"); - } - - if (!string.IsNullOrEmpty(configuration.DesktopExec)) - { - stringBuilder.Append($"Exec={configuration.DesktopExec}\n"); - } - else - { - // 这里不能使用 Path.Join 方法,因为如果在 Windows 上进行打包,会将 \ 替换为 /,导致打包失败 - //var exec = Path.Join("/opt/apps", appId, "files", "bin", configuration.AssemblyName); - var exec = $"/opt/apps/{appId}/files/bin/{configuration.AssemblyName}"; - stringBuilder.Append($"Exec={exec}\n"); - } - - if (!string.IsNullOrEmpty(configuration.DesktopIcon)) - { - stringBuilder.Append($"Icon={configuration.DesktopIcon}\n"); - } - else - { - stringBuilder.Append($"Icon={appId}\n"); - } - - if (!string.IsNullOrEmpty(configuration.DesktopMimeType)) - { - stringBuilder.Append($"MimeType={configuration.DesktopMimeType}\n"); - } - - File.WriteAllText(desktopFile, stringBuilder.ToString(), encoding); - } - - // opt\apps\AppId\entries\icons - if (File.Exists(configuration.SvgIconFile)) - { - var svgFile = Path.Join(entriesFolder, "icons", "hicolor", "scalable", "apps", $"{appId}.svg"); - Directory.CreateDirectory(Path.GetDirectoryName(svgFile)!); - File.Copy(configuration.SvgIconFile, svgFile); - } - else - { - bool anyIconFileExist = false; - foreach (var (iconFile, resolution) in new (string? iconFile, string resolution)[] - { - (configuration.Png16x16IconFile, "16x16"), - (configuration.Png24x24IconFile, "24x24"), - (configuration.Png32x32IconFile, "32x32"), - (configuration.Png48x48IconFile, "48x48"), - (configuration.Png128x128IconFile, "128x128"), - (configuration.Png256x256IconFile, "256x256"), - (configuration.Png512x512IconFile,"512x512"), - }) - { - if (File.Exists(iconFile)) - { - var pngFile = Path.Join(entriesFolder, "icons", "hicolor", resolution, "apps", $"{appId}.png"); - Directory.CreateDirectory(Path.GetDirectoryName(pngFile)!); - File.Copy(iconFile, pngFile); - - anyIconFileExist = true; - } - } - - if (!anyIconFileExist) - { - Logger.LogWarning("找不到任何的图标文件,将导致应用无法在开始菜单显示。可通过 SvgIconFile 配置矢量图,可通过 Png16x16IconFile 等属性配置不同分辨率的图标"); - } - } - - // opt\apps\AppId\info - var infoJsonFile = Path.Join(appIdFolder, "info"); - if (File.Exists(configuration.DebInfoFile)) - { - File.Copy(configuration.DebInfoFile, infoJsonFile); - } - else - { - var data = new ApplicationInfoFileData() - { - AppId = appId, - ApplicationName = configuration.AppName, - Version = configuration.Version, - Architecture = configuration.Architecture.Split(';') - }; - - var permissions = configuration.InfoPermissions?.Split(';'); - if (permissions != null && permissions.Length > 0) - { - var applicationInfoPermissions = new ApplicationInfoPermissions(); - foreach (var permission in permissions) - { - switch (permission) - { - case "autostart": - applicationInfoPermissions.Autostart = true; - break; - case "notification": - applicationInfoPermissions.Notification = true; - break; - case "trayicon": - applicationInfoPermissions.TrayIcon = true; - break; - case "clipboard": - applicationInfoPermissions.Clipboard = true; - break; - case "account": - applicationInfoPermissions.Account = true; - break; - case "bluetooth": - applicationInfoPermissions.Bluetooth = true; - break; - case "camera": - applicationInfoPermissions.Camera = true; - break; - case "audio_record": - applicationInfoPermissions.AudioRecord = true; - break; - case "installed_apps": - applicationInfoPermissions.InstalledApps = true; - break; - } - } - data.Permissions = applicationInfoPermissions; - } - - var json = JsonSerializer.Serialize(data,new JsonSerializerOptions() - { - WriteIndented = true, - }).Replace("\r\n","\n"); - File.WriteAllText(infoJsonFile, json, encoding); - } - - // 创建 control 文件 - var controlFile = Path.Join(packingFolder, "DEBIAN", "control"); - Directory.CreateDirectory(Path.GetDirectoryName(controlFile)!); - if (File.Exists(configuration.DebControlFile)) - { - File.Copy(configuration.DebControlFile, controlFile); - } - else - { - var stringBuilder = new StringBuilder(); - stringBuilder - .Append($"Package: {appId}\n") - .Append($"Version: {configuration.Version}\n") - .Append($"Section: {configuration.DebControlSection}\n") - .Append($"Priority: {configuration.DebControlPriority}\n") - .Append($"Architecture: {configuration.Architecture}\n") - .Append($"Multi-Arch: {configuration.DebControlMultiArch}\n") - .Append($"Build-Depends: {configuration.DebControlBuildDepends}\n") - .Append($"Standards-Version: {configuration.DebControlStandardsVersion}\n") - .Append($"Homepage: {configuration.DebControlHomepage}\n") - ; - if (!string.IsNullOrEmpty(configuration.DebControlMaintainer)) - { - stringBuilder.Append($"Maintainer: {configuration.DebControlMaintainer}\n"); - } - else - { - Logger.LogWarning($"没有找到 DebControlMaintainer 属性配置。安装完成可能在开始菜单找不到应用。请配置 deb 包的维护者,如 lindexi 格式"); - } - - if (!string.IsNullOrEmpty(configuration.DebControlDescription)) - { - stringBuilder.Append($"Description: {configuration.DebControlDescription}\n"); - } - else - { - Logger.LogWarning($"没有找到 DebControlDescription 属性配置。安装完成可能在开始菜单找不到应用。请配置 deb 包的描述,描述可使用中文"); - } - - File.WriteAllText(controlFile, stringBuilder.ToString(), encoding); - } - } - public void PackageDeb(DirectoryInfo packingFolder, FileInfo outputDebFile, DirectoryInfo? workingFolder = null) { Logger.LogInformation($"Start packing UOS deb from '{packingFolder.FullName}' to '{outputDebFile.FullName}'"); diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs index ce2861c..64c0f61 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs @@ -1,9 +1,305 @@ -namespace Packing.DebUOS; +using Microsoft.Extensions.Logging; +using Packing.DebUOS.Contexts; +using System.IO; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Text; + +namespace Packing.DebUOS; + +/// +/// 创建符合 UOS 安装包制作规范的打包文件夹 +/// +/// 打包细节请参阅 [一步步教你在 Windows 上构建 dotnet 系应用的 UOS 软件安装包](https://blog.lindexi.com/post/%E4%B8%80%E6%AD%A5%E6%AD%A5%E6%95%99%E4%BD%A0%E5%9C%A8-Windows-%E4%B8%8A%E6%9E%84%E5%BB%BA-dotnet-%E7%B3%BB%E5%BA%94%E7%94%A8%E7%9A%84-UOS-%E8%BD%AF%E4%BB%B6%E5%AE%89%E8%A3%85%E5%8C%85.html ) +// ReSharper disable once InconsistentNaming public class DebUOSPackageFileStructCreator { - public void CreateDebUOSPackagingFolder(DebUOSConfiguration configuration) + public DebUOSPackageFileStructCreator(ILogger logger) { + Logger = logger; + } + + public ILogger Logger { get; } + + public void CreatePackagingFolder(DebUOSConfiguration configuration) + { + var projectPublishFolder = configuration.ProjectPublishFolder; + if (!Directory.Exists(projectPublishFolder)) + { + Logger.LogError($"Project publish folder '{projectPublishFolder}' not exist"); + return; + } + + var workingFolder = configuration.WorkingFolder; + if (string.IsNullOrEmpty(workingFolder)) + { + workingFolder = Path.Join(Path.GetTempPath(), "DebUOSPacking", + $"{configuration.AssemblyName}_{Path.GetRandomFileName()}"); + } + + Directory.CreateDirectory(workingFolder); + configuration.WorkingFolder = workingFolder; + + var packingFolder = configuration.PackingFolder; + if (string.IsNullOrEmpty(packingFolder)) + { + packingFolder = Path.Join(workingFolder, "Packing"); + } + + // 删除旧的文件夹,防止打包使用到旧文件 + if (Directory.Exists(packingFolder)) + { + Directory.Delete(packingFolder, true); + } + + Directory.CreateDirectory(packingFolder); + configuration.PackingFolder = packingFolder; + + var appId = configuration.UOSAppId; + if (string.IsNullOrEmpty(appId)) + { + Logger.LogError($"找不到 UOS 的 AppId 内容,请确保已经配置 UOSAppId 属性"); + return; + } + + var match = Regex.Match(appId, @"[a-z\.]+"); + if (!match.Success || match.Value != appId) + { + Logger.LogError( + $"UOS 的 AppId 内容不符合规范,请确保配置的 UOSAppId 属性符合规范。请务必使用厂商的倒置域名+产品名作为应用包名,如 `com.example.demo` 格式,且只允许使用小写字母"); + return; + } + + // opt\apps\AppId\ + // opt\apps\AppId\files + var appIdFolder = Path.Join(packingFolder, "opt", "apps", appId); + var filesFolder = Path.Join(appIdFolder, "files"); + Directory.CreateDirectory(filesFolder); + var applicationBin = Path.Join(filesFolder, "bin"); + // 符号比拷贝速度快 + var symbol = Directory.CreateSymbolicLink(applicationBin, projectPublishFolder); + if (!symbol.Exists) + { + Logger.LogError($"创建符号链接失败,从 '{projectPublishFolder}' 到 '{applicationBin}'"); + return; + } + + // opt\apps\AppId\entries + // opt\apps\AppId\entries\applications + var entriesFolder = Path.Join(appIdFolder, "entries"); + var applicationsFolder = Path.Join(entriesFolder, "applications"); + var desktopFile = Path.Join(applicationsFolder, $"{appId}.desktop"); + Directory.CreateDirectory(applicationsFolder); + + // 不能使用 Encoding.UTF8 编码,因为默认会写入 BOM 导致 deb 打包失败 + var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + + if (File.Exists(configuration.DebDesktopFile)) + { + // 开发者配置了自定义的文件,则使用开发者的文件 + File.Copy(configuration.DebDesktopFile, desktopFile); + } + else + { + var stringBuilder = new StringBuilder(); + // 这里不能使用 AppendLine 方法,保持换行使用 \n 字符 + stringBuilder + .Append("[Desktop Entry]\n") + .Append($"Categories={configuration.DesktopCategories}\n") + .Append($"Name={configuration.AppName}\n") + .Append($"Keywords={configuration.DesktopKeywords}\n") + .Append($"Comment={configuration.DesktopComment}\n") + .Append($"Type={configuration.DesktopType}\n") + .Append($"Terminal={configuration.DesktopTerminal.ToString().ToLowerInvariant()}\n") + .Append($"StartupNotify={configuration.DesktopStartupNotify.ToString().ToLowerInvariant()}\n"); + + if (!string.IsNullOrEmpty(configuration.AppNameZhCN)) + { + stringBuilder.Append($"Name[zh_CN]={configuration.AppNameZhCN}\n"); + } + + if (!string.IsNullOrEmpty(configuration.DesktopKeywordsZhCN)) + { + stringBuilder.Append($"Keywords[zh_CN]={configuration.DesktopKeywordsZhCN}\n"); + } + + if (!string.IsNullOrEmpty(configuration.DesktopCommentZhCN)) + { + stringBuilder.Append($"Comment[zh_CN]={configuration.DesktopCommentZhCN}\n"); + } + + if (!string.IsNullOrEmpty(configuration.DesktopExec)) + { + stringBuilder.Append($"Exec={configuration.DesktopExec}\n"); + } + else + { + // 这里不能使用 Path.Join 方法,因为如果在 Windows 上进行打包,会将 \ 替换为 /,导致打包失败 + //var exec = Path.Join("/opt/apps", appId, "files", "bin", configuration.AssemblyName); + var exec = $"/opt/apps/{appId}/files/bin/{configuration.AssemblyName}"; + stringBuilder.Append($"Exec={exec}\n"); + } + + if (!string.IsNullOrEmpty(configuration.DesktopIcon)) + { + stringBuilder.Append($"Icon={configuration.DesktopIcon}\n"); + } + else + { + stringBuilder.Append($"Icon={appId}\n"); + } + + if (!string.IsNullOrEmpty(configuration.DesktopMimeType)) + { + stringBuilder.Append($"MimeType={configuration.DesktopMimeType}\n"); + } + + File.WriteAllText(desktopFile, stringBuilder.ToString(), encoding); + } + + // opt\apps\AppId\entries\icons + if (File.Exists(configuration.SvgIconFile)) + { + var svgFile = Path.Join(entriesFolder, "icons", "hicolor", "scalable", "apps", $"{appId}.svg"); + Directory.CreateDirectory(Path.GetDirectoryName(svgFile)!); + File.Copy(configuration.SvgIconFile, svgFile); + } + else + { + bool anyIconFileExist = false; + foreach (var (iconFile, resolution) in new (string? iconFile, string resolution)[] + { + (configuration.Png16x16IconFile, "16x16"), + (configuration.Png24x24IconFile, "24x24"), + (configuration.Png32x32IconFile, "32x32"), + (configuration.Png48x48IconFile, "48x48"), + (configuration.Png128x128IconFile, "128x128"), + (configuration.Png256x256IconFile, "256x256"), + (configuration.Png512x512IconFile, "512x512"), + }) + { + if (File.Exists(iconFile)) + { + var pngFile = Path.Join(entriesFolder, "icons", "hicolor", resolution, "apps", $"{appId}.png"); + Directory.CreateDirectory(Path.GetDirectoryName(pngFile)!); + File.Copy(iconFile, pngFile); + + anyIconFileExist = true; + } + } + + if (!anyIconFileExist) + { + Logger.LogWarning("找不到任何的图标文件,将导致应用无法在开始菜单显示。可通过 SvgIconFile 配置矢量图,可通过 Png16x16IconFile 等属性配置不同分辨率的图标"); + } + } + + // opt\apps\AppId\info + var infoJsonFile = Path.Join(appIdFolder, "info"); + if (File.Exists(configuration.DebInfoFile)) + { + File.Copy(configuration.DebInfoFile, infoJsonFile); + } + else + { + var data = new ApplicationInfoFileData() + { + AppId = appId, + ApplicationName = configuration.AppName, + Version = configuration.Version, + Architecture = configuration.Architecture.Split(';') + }; + + var permissions = configuration.InfoPermissions?.Split(';'); + if (permissions != null && permissions.Length > 0) + { + var applicationInfoPermissions = new ApplicationInfoPermissions(); + foreach (var permission in permissions) + { + switch (permission) + { + case "autostart": + applicationInfoPermissions.Autostart = true; + break; + case "notification": + applicationInfoPermissions.Notification = true; + break; + case "trayicon": + applicationInfoPermissions.TrayIcon = true; + break; + case "clipboard": + applicationInfoPermissions.Clipboard = true; + break; + case "account": + applicationInfoPermissions.Account = true; + break; + case "bluetooth": + applicationInfoPermissions.Bluetooth = true; + break; + case "camera": + applicationInfoPermissions.Camera = true; + break; + case "audio_record": + applicationInfoPermissions.AudioRecord = true; + break; + case "installed_apps": + applicationInfoPermissions.InstalledApps = true; + break; + } + } + + data.Permissions = applicationInfoPermissions; + } + + var json = JsonSerializer.Serialize(data, new JsonSerializerOptions() + { + WriteIndented = true, + }).Replace("\r\n", "\n"); + File.WriteAllText(infoJsonFile, json, encoding); + } + + // 创建 control 文件 + var controlFile = Path.Join(packingFolder, "DEBIAN", "control"); + Directory.CreateDirectory(Path.GetDirectoryName(controlFile)!); + if (File.Exists(configuration.DebControlFile)) + { + File.Copy(configuration.DebControlFile, controlFile); + } + else + { + var stringBuilder = new StringBuilder(); + stringBuilder + .Append($"Package: {appId}\n") + .Append($"Version: {configuration.Version}\n") + .Append($"Section: {configuration.DebControlSection}\n") + .Append($"Priority: {configuration.DebControlPriority}\n") + .Append($"Architecture: {configuration.Architecture}\n") + .Append($"Multi-Arch: {configuration.DebControlMultiArch}\n") + .Append($"Build-Depends: {configuration.DebControlBuildDepends}\n") + .Append($"Standards-Version: {configuration.DebControlStandardsVersion}\n") + .Append($"Homepage: {configuration.DebControlHomepage}\n") + ; + if (!string.IsNullOrEmpty(configuration.DebControlMaintainer)) + { + stringBuilder.Append($"Maintainer: {configuration.DebControlMaintainer}\n"); + } + else + { + Logger.LogWarning( + $"没有找到 DebControlMaintainer 属性配置。安装完成可能在开始菜单找不到应用。请配置 deb 包的维护者,如 lindexi 格式"); + } + + if (!string.IsNullOrEmpty(configuration.DebControlDescription)) + { + stringBuilder.Append($"Description: {configuration.DebControlDescription}\n"); + } + else + { + Logger.LogWarning($"没有找到 DebControlDescription 属性配置。安装完成可能在开始菜单找不到应用。请配置 deb 包的描述,描述可使用中文"); + } + File.WriteAllText(controlFile, stringBuilder.ToString(), encoding); + } } } \ No newline at end of file From b9074e8de054edb2de04df98b6513320e432504b Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 09:36:26 +0800 Subject: [PATCH 46/74] =?UTF-8?q?=E5=B0=86=E9=85=8D=E7=BD=AE=E6=94=BE?= =?UTF-8?q?=E5=85=A5=E5=88=B0=E6=AD=A3=E7=A1=AE=E5=91=BD=E5=90=8D=E7=A9=BA?= =?UTF-8?q?=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packing.DebUOS.Tool/Program.cs | 1 + .../{ => Contexts/Configurations}/DebUOSConfiguration.cs | 9 ++------- DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs | 1 + 3 files changed, 4 insertions(+), 7 deletions(-) rename DebUOS/Packing.DebUOS/{ => Contexts/Configurations}/DebUOSConfiguration.cs (97%) diff --git a/DebUOS/Packing.DebUOS.Tool/Program.cs b/DebUOS/Packing.DebUOS.Tool/Program.cs index 0961c0d..5bca8aa 100644 --- a/DebUOS/Packing.DebUOS.Tool/Program.cs +++ b/DebUOS/Packing.DebUOS.Tool/Program.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; using Packing.DebUOS; +using Packing.DebUOS.Contexts.Configurations; using Packing.DebUOS.Tool; var options = CommandLine.Parse(args).As(); diff --git a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs b/DebUOS/Packing.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs similarity index 97% rename from DebUOS/Packing.DebUOS/DebUOSConfiguration.cs rename to DebUOS/Packing.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs index 50d06a3..6371001 100644 --- a/DebUOS/Packing.DebUOS/DebUOSConfiguration.cs +++ b/DebUOS/Packing.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs @@ -1,14 +1,9 @@ // ReSharper disable InconsistentNaming -using dotnetCampus.Configurations; -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using dotnetCampus.Configurations; -namespace Packing.DebUOS; +namespace Packing.DebUOS.Contexts.Configurations; public class DebUOSConfiguration : Configuration { diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs b/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs index 64c0f61..1022fb4 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs +++ b/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs @@ -5,6 +5,7 @@ using System.Text.Json; using System.Text.RegularExpressions; using System.Text; +using Packing.DebUOS.Contexts.Configurations; namespace Packing.DebUOS; From 62ff9e5288c8fd73cd4b62402eaa8a66161031b2 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 10:05:27 +0800 Subject: [PATCH 47/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Build/package.props | 0 .../Build/package.targets | 0 .../Options.cs | 0 .../Packaging.DebUOS.Tool.csproj} | 2 +- .../Program.cs | 0 .../Properties/launchSettings.json | 0 .../Contexts/ApplicationInfoFileData.cs | 0 .../Contexts/ApplicationInfoPermissions.cs | 0 .../Configurations/DebUOSConfiguration.cs | 0 .../DebUOSPackageCreator.cs | 0 .../DebUOSPackageFileStructCreator.cs | 4 +- .../Packaging.DebUOS.csproj} | 0 .../Packaging.Targets/AssemblyAttributes.cs | 2 +- DotNETBuild.sln | 58 +++++++++---------- build/Version.props | 2 +- 15 files changed, 35 insertions(+), 33 deletions(-) rename DebUOS/{Packing.DebUOS.Tool => Packaging.DebUOS.Tool}/Build/package.props (100%) rename DebUOS/{Packing.DebUOS.Tool => Packaging.DebUOS.Tool}/Build/package.targets (100%) rename DebUOS/{Packing.DebUOS.Tool => Packaging.DebUOS.Tool}/Options.cs (100%) rename DebUOS/{Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj => Packaging.DebUOS.Tool/Packaging.DebUOS.Tool.csproj} (95%) rename DebUOS/{Packing.DebUOS.Tool => Packaging.DebUOS.Tool}/Program.cs (100%) rename DebUOS/{Packing.DebUOS.Tool => Packaging.DebUOS.Tool}/Properties/launchSettings.json (100%) rename DebUOS/{Packing.DebUOS => Packaging.DebUOS}/Contexts/ApplicationInfoFileData.cs (100%) rename DebUOS/{Packing.DebUOS => Packaging.DebUOS}/Contexts/ApplicationInfoPermissions.cs (100%) rename DebUOS/{Packing.DebUOS => Packaging.DebUOS}/Contexts/Configurations/DebUOSConfiguration.cs (100%) rename DebUOS/{Packing.DebUOS => Packaging.DebUOS}/DebUOSPackageCreator.cs (100%) rename DebUOS/{Packing.DebUOS => Packaging.DebUOS}/DebUOSPackageFileStructCreator.cs (99%) rename DebUOS/{Packing.DebUOS/Packing.DebUOS.csproj => Packaging.DebUOS/Packaging.DebUOS.csproj} (100%) diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.props b/DebUOS/Packaging.DebUOS.Tool/Build/package.props similarity index 100% rename from DebUOS/Packing.DebUOS.Tool/Build/package.props rename to DebUOS/Packaging.DebUOS.Tool/Build/package.props diff --git a/DebUOS/Packing.DebUOS.Tool/Build/package.targets b/DebUOS/Packaging.DebUOS.Tool/Build/package.targets similarity index 100% rename from DebUOS/Packing.DebUOS.Tool/Build/package.targets rename to DebUOS/Packaging.DebUOS.Tool/Build/package.targets diff --git a/DebUOS/Packing.DebUOS.Tool/Options.cs b/DebUOS/Packaging.DebUOS.Tool/Options.cs similarity index 100% rename from DebUOS/Packing.DebUOS.Tool/Options.cs rename to DebUOS/Packaging.DebUOS.Tool/Options.cs diff --git a/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj b/DebUOS/Packaging.DebUOS.Tool/Packaging.DebUOS.Tool.csproj similarity index 95% rename from DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj rename to DebUOS/Packaging.DebUOS.Tool/Packaging.DebUOS.Tool.csproj index f2eae6a..27c03cb 100644 --- a/DebUOS/Packing.DebUOS.Tool/Packing.DebUOS.Tool.csproj +++ b/DebUOS/Packaging.DebUOS.Tool/Packaging.DebUOS.Tool.csproj @@ -26,7 +26,7 @@ - + all diff --git a/DebUOS/Packing.DebUOS.Tool/Program.cs b/DebUOS/Packaging.DebUOS.Tool/Program.cs similarity index 100% rename from DebUOS/Packing.DebUOS.Tool/Program.cs rename to DebUOS/Packaging.DebUOS.Tool/Program.cs diff --git a/DebUOS/Packing.DebUOS.Tool/Properties/launchSettings.json b/DebUOS/Packaging.DebUOS.Tool/Properties/launchSettings.json similarity index 100% rename from DebUOS/Packing.DebUOS.Tool/Properties/launchSettings.json rename to DebUOS/Packaging.DebUOS.Tool/Properties/launchSettings.json diff --git a/DebUOS/Packing.DebUOS/Contexts/ApplicationInfoFileData.cs b/DebUOS/Packaging.DebUOS/Contexts/ApplicationInfoFileData.cs similarity index 100% rename from DebUOS/Packing.DebUOS/Contexts/ApplicationInfoFileData.cs rename to DebUOS/Packaging.DebUOS/Contexts/ApplicationInfoFileData.cs diff --git a/DebUOS/Packing.DebUOS/Contexts/ApplicationInfoPermissions.cs b/DebUOS/Packaging.DebUOS/Contexts/ApplicationInfoPermissions.cs similarity index 100% rename from DebUOS/Packing.DebUOS/Contexts/ApplicationInfoPermissions.cs rename to DebUOS/Packaging.DebUOS/Contexts/ApplicationInfoPermissions.cs diff --git a/DebUOS/Packing.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs similarity index 100% rename from DebUOS/Packing.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs rename to DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs similarity index 100% rename from DebUOS/Packing.DebUOS/DebUOSPackageCreator.cs rename to DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs diff --git a/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs b/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs similarity index 99% rename from DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs rename to DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs index 1022fb4..fa82b5b 100644 --- a/DebUOS/Packing.DebUOS/DebUOSPackageFileStructCreator.cs +++ b/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs @@ -204,6 +204,8 @@ public void CreatePackagingFolder(DebUOSConfiguration configuration) } else { + var infoPermissions = configuration.InfoPermissions; + var data = new ApplicationInfoFileData() { AppId = appId, @@ -212,7 +214,7 @@ public void CreatePackagingFolder(DebUOSConfiguration configuration) Architecture = configuration.Architecture.Split(';') }; - var permissions = configuration.InfoPermissions?.Split(';'); + var permissions = infoPermissions?.Split(';'); if (permissions != null && permissions.Length > 0) { var applicationInfoPermissions = new ApplicationInfoPermissions(); diff --git a/DebUOS/Packing.DebUOS/Packing.DebUOS.csproj b/DebUOS/Packaging.DebUOS/Packaging.DebUOS.csproj similarity index 100% rename from DebUOS/Packing.DebUOS/Packing.DebUOS.csproj rename to DebUOS/Packaging.DebUOS/Packaging.DebUOS.csproj diff --git a/DebUOS/Packaging.Targets/AssemblyAttributes.cs b/DebUOS/Packaging.Targets/AssemblyAttributes.cs index d2d6a2f..9a1ba6f 100644 --- a/DebUOS/Packaging.Targets/AssemblyAttributes.cs +++ b/DebUOS/Packaging.Targets/AssemblyAttributes.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Packing.DebUOS")] \ No newline at end of file +[assembly: InternalsVisibleTo("Packaging.DebUOS")] \ No newline at end of file diff --git a/DotNETBuild.sln b/DotNETBuild.sln index 3a449ad..006a4a7 100644 --- a/DotNETBuild.sln +++ b/DotNETBuild.sln @@ -91,11 +91,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SyncTool", "SyncTool\SyncTo EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DebUOS", "DebUOS", "{AC990428-ACB7-46A9-B66A-AF0557A7D0C6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Packing.DebUOS", "DebUOS\Packing.DebUOS\Packing.DebUOS.csproj", "{209825D6-7821-4E0E-B3C8-E3504FF65B36}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Packaging.Targets", "DebUOS\Packaging.Targets\Packaging.Targets.csproj", "{C69F9A99-8110-4379-A22C-176B3E05E481}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Packing.DebUOS.Tool", "DebUOS\Packing.DebUOS.Tool\Packing.DebUOS.Tool.csproj", "{4FFC6853-8CF6-4C87-9EA7-B04811DCD051}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Packaging.DebUOS", "DebUOS\Packaging.DebUOS\Packaging.DebUOS.csproj", "{125615F2-0873-4C66-8FB3-E334D2F5BE9C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Packaging.DebUOS.Tool", "DebUOS\Packaging.DebUOS.Tool\Packaging.DebUOS.Tool.csproj", "{A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -479,18 +479,6 @@ Global {4360E6F1-680C-45D7-A4E0-1663A237709A}.Release|x64.Build.0 = Release|Any CPU {4360E6F1-680C-45D7-A4E0-1663A237709A}.Release|x86.ActiveCfg = Release|Any CPU {4360E6F1-680C-45D7-A4E0-1663A237709A}.Release|x86.Build.0 = Release|Any CPU - {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Debug|Any CPU.Build.0 = Debug|Any CPU - {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Debug|x64.ActiveCfg = Debug|Any CPU - {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Debug|x64.Build.0 = Debug|Any CPU - {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Debug|x86.ActiveCfg = Debug|Any CPU - {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Debug|x86.Build.0 = Debug|Any CPU - {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|Any CPU.ActiveCfg = Release|Any CPU - {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|Any CPU.Build.0 = Release|Any CPU - {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|x64.ActiveCfg = Release|Any CPU - {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|x64.Build.0 = Release|Any CPU - {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|x86.ActiveCfg = Release|Any CPU - {209825D6-7821-4E0E-B3C8-E3504FF65B36}.Release|x86.Build.0 = Release|Any CPU {C69F9A99-8110-4379-A22C-176B3E05E481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C69F9A99-8110-4379-A22C-176B3E05E481}.Debug|Any CPU.Build.0 = Debug|Any CPU {C69F9A99-8110-4379-A22C-176B3E05E481}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -503,18 +491,30 @@ Global {C69F9A99-8110-4379-A22C-176B3E05E481}.Release|x64.Build.0 = Release|Any CPU {C69F9A99-8110-4379-A22C-176B3E05E481}.Release|x86.ActiveCfg = Release|Any CPU {C69F9A99-8110-4379-A22C-176B3E05E481}.Release|x86.Build.0 = Release|Any CPU - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Debug|x64.ActiveCfg = Debug|Any CPU - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Debug|x64.Build.0 = Debug|Any CPU - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Debug|x86.ActiveCfg = Debug|Any CPU - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Debug|x86.Build.0 = Debug|Any CPU - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Release|Any CPU.Build.0 = Release|Any CPU - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Release|x64.ActiveCfg = Release|Any CPU - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Release|x64.Build.0 = Release|Any CPU - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Release|x86.ActiveCfg = Release|Any CPU - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051}.Release|x86.Build.0 = Release|Any CPU + {125615F2-0873-4C66-8FB3-E334D2F5BE9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {125615F2-0873-4C66-8FB3-E334D2F5BE9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {125615F2-0873-4C66-8FB3-E334D2F5BE9C}.Debug|x64.ActiveCfg = Debug|Any CPU + {125615F2-0873-4C66-8FB3-E334D2F5BE9C}.Debug|x64.Build.0 = Debug|Any CPU + {125615F2-0873-4C66-8FB3-E334D2F5BE9C}.Debug|x86.ActiveCfg = Debug|Any CPU + {125615F2-0873-4C66-8FB3-E334D2F5BE9C}.Debug|x86.Build.0 = Debug|Any CPU + {125615F2-0873-4C66-8FB3-E334D2F5BE9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {125615F2-0873-4C66-8FB3-E334D2F5BE9C}.Release|Any CPU.Build.0 = Release|Any CPU + {125615F2-0873-4C66-8FB3-E334D2F5BE9C}.Release|x64.ActiveCfg = Release|Any CPU + {125615F2-0873-4C66-8FB3-E334D2F5BE9C}.Release|x64.Build.0 = Release|Any CPU + {125615F2-0873-4C66-8FB3-E334D2F5BE9C}.Release|x86.ActiveCfg = Release|Any CPU + {125615F2-0873-4C66-8FB3-E334D2F5BE9C}.Release|x86.Build.0 = Release|Any CPU + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Debug|x64.ActiveCfg = Debug|Any CPU + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Debug|x64.Build.0 = Debug|Any CPU + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Debug|x86.ActiveCfg = Debug|Any CPU + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Debug|x86.Build.0 = Debug|Any CPU + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Release|Any CPU.Build.0 = Release|Any CPU + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Release|x64.ActiveCfg = Release|Any CPU + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Release|x64.Build.0 = Release|Any CPU + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Release|x86.ActiveCfg = Release|Any CPU + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -535,9 +535,9 @@ Global {E252DBCC-6FC9-42FF-86DB-E31D076F954F} = {2C43AF3C-C3B2-4B8A-81E2-DA6D8A53356A} {93037B85-0A27-4A6E-B485-2F4544ABEFDF} = {2C43AF3C-C3B2-4B8A-81E2-DA6D8A53356A} {4360E6F1-680C-45D7-A4E0-1663A237709A} = {3C8DD4B9-55DB-450E-B06A-B7C4EB3A9CF1} - {209825D6-7821-4E0E-B3C8-E3504FF65B36} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} {C69F9A99-8110-4379-A22C-176B3E05E481} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} - {4FFC6853-8CF6-4C87-9EA7-B04811DCD051} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} + {125615F2-0873-4C66-8FB3-E334D2F5BE9C} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} + {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A6E377C6-EDE0-4135-951F-78C62B63DE96} diff --git a/build/Version.props b/build/Version.props index b8983a1..94933d3 100644 --- a/build/Version.props +++ b/build/Version.props @@ -1,5 +1,5 @@ - 1.2.1-alpha16 + 1.2.1-alpha17 \ No newline at end of file From 7d0b60ebdb251b05c61eb916f9bf97cdb6c6e3db Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 10:05:51 +0800 Subject: [PATCH 48/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E7=A9=BA=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.Tool/Options.cs | 10 ++-------- DebUOS/Packaging.DebUOS.Tool/Program.cs | 5 +++-- .../Contexts/ApplicationInfoFileData.cs | 2 +- .../Contexts/ApplicationInfoPermissions.cs | 2 +- .../Contexts/Configurations/DebUOSConfiguration.cs | 2 +- DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs | 6 +----- .../DebUOSPackageFileStructCreator.cs | 13 ++++++------- 7 files changed, 15 insertions(+), 25 deletions(-) diff --git a/DebUOS/Packaging.DebUOS.Tool/Options.cs b/DebUOS/Packaging.DebUOS.Tool/Options.cs index 3965ffc..dd2d385 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Options.cs +++ b/DebUOS/Packaging.DebUOS.Tool/Options.cs @@ -1,12 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using dotnetCampus.Cli; -using dotnetCampus.Cli; - -namespace Packing.DebUOS.Tool; +namespace Packaging.DebUOS.Tool; /// /// 命令行参数 diff --git a/DebUOS/Packaging.DebUOS.Tool/Program.cs b/DebUOS/Packaging.DebUOS.Tool/Program.cs index 5bca8aa..6332f08 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Program.cs +++ b/DebUOS/Packaging.DebUOS.Tool/Program.cs @@ -5,9 +5,10 @@ using dotnetCampus.Configurations.Core; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; +using Packaging.DebUOS; +using Packaging.DebUOS.Contexts.Configurations; +using Packaging.DebUOS.Tool; using Packing.DebUOS; -using Packing.DebUOS.Contexts.Configurations; -using Packing.DebUOS.Tool; var options = CommandLine.Parse(args).As(); diff --git a/DebUOS/Packaging.DebUOS/Contexts/ApplicationInfoFileData.cs b/DebUOS/Packaging.DebUOS/Contexts/ApplicationInfoFileData.cs index 09ec21c..90b9cc2 100644 --- a/DebUOS/Packaging.DebUOS/Contexts/ApplicationInfoFileData.cs +++ b/DebUOS/Packaging.DebUOS/Contexts/ApplicationInfoFileData.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace Packing.DebUOS.Contexts; +namespace Packaging.DebUOS.Contexts; /// /// 用于写入到 opt\apps\${AppId}\info 文件的数据内容 diff --git a/DebUOS/Packaging.DebUOS/Contexts/ApplicationInfoPermissions.cs b/DebUOS/Packaging.DebUOS/Contexts/ApplicationInfoPermissions.cs index 8b93907..77f4c02 100644 --- a/DebUOS/Packaging.DebUOS/Contexts/ApplicationInfoPermissions.cs +++ b/DebUOS/Packaging.DebUOS/Contexts/ApplicationInfoPermissions.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Packing.DebUOS.Contexts; +namespace Packaging.DebUOS.Contexts; class ApplicationInfoPermissions { diff --git a/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs index 6371001..18b53bf 100644 --- a/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs +++ b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs @@ -3,7 +3,7 @@ using System.IO; using dotnetCampus.Configurations; -namespace Packing.DebUOS.Contexts.Configurations; +namespace Packaging.DebUOS.Contexts.Configurations; public class DebUOSConfiguration : Configuration { diff --git a/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs index 770db3c..07c6479 100644 --- a/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs @@ -4,15 +4,11 @@ using System.IO.Compression; using System.Linq; using System.Text; -using System.Text.Json; -using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; - using Packaging.Targets; using Packaging.Targets.IO; -using Packing.DebUOS.Contexts; -namespace Packing.DebUOS; +namespace Packaging.DebUOS; /// /// 打出 UOS 的 deb 包 diff --git a/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs b/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs index fa82b5b..525076a 100644 --- a/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs +++ b/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs @@ -1,13 +1,12 @@ -using Microsoft.Extensions.Logging; - -using Packing.DebUOS.Contexts; -using System.IO; +using System.IO; +using System.Text; using System.Text.Json; using System.Text.RegularExpressions; -using System.Text; -using Packing.DebUOS.Contexts.Configurations; +using Microsoft.Extensions.Logging; +using Packaging.DebUOS.Contexts; +using Packaging.DebUOS.Contexts.Configurations; -namespace Packing.DebUOS; +namespace Packaging.DebUOS; /// /// 创建符合 UOS 安装包制作规范的打包文件夹 From 0fa0486354f3e46fc747f979e879d17baec0f197 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 14:30:47 +0800 Subject: [PATCH 49/74] =?UTF-8?q?=E6=8B=86=E5=88=86=E4=B8=A4=E4=B8=AA?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E6=8F=90=E4=BE=9B=E5=8D=95=E7=8B=AC=E7=9A=84?= =?UTF-8?q?=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Build/package.props | 0 .../Build/package.targets | 0 .../Packaging.DebUOS.NuGet.csproj | 36 +++++++++++++++++++ .../Packaging.DebUOS.Tool.csproj | 8 ++--- DebUOS/Packaging.DebUOS.Tool/Program.cs | 1 - DotNETBuild.sln | 15 ++++++++ 6 files changed, 53 insertions(+), 7 deletions(-) rename DebUOS/{Packaging.DebUOS.Tool => Packaging.DebUOS.NuGet}/Build/package.props (100%) rename DebUOS/{Packaging.DebUOS.Tool => Packaging.DebUOS.NuGet}/Build/package.targets (100%) create mode 100644 DebUOS/Packaging.DebUOS.NuGet/Packaging.DebUOS.NuGet.csproj diff --git a/DebUOS/Packaging.DebUOS.Tool/Build/package.props b/DebUOS/Packaging.DebUOS.NuGet/Build/package.props similarity index 100% rename from DebUOS/Packaging.DebUOS.Tool/Build/package.props rename to DebUOS/Packaging.DebUOS.NuGet/Build/package.props diff --git a/DebUOS/Packaging.DebUOS.Tool/Build/package.targets b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets similarity index 100% rename from DebUOS/Packaging.DebUOS.Tool/Build/package.targets rename to DebUOS/Packaging.DebUOS.NuGet/Build/package.targets diff --git a/DebUOS/Packaging.DebUOS.NuGet/Packaging.DebUOS.NuGet.csproj b/DebUOS/Packaging.DebUOS.NuGet/Packaging.DebUOS.NuGet.csproj new file mode 100644 index 0000000..4a46b00 --- /dev/null +++ b/DebUOS/Packaging.DebUOS.NuGet/Packaging.DebUOS.NuGet.csproj @@ -0,0 +1,36 @@ + + + + net6.0 + + + + false + + + true + + + + true + MIT + true + Packaging.DebUOS + + + + + + all + + + + + + + + + + + + diff --git a/DebUOS/Packaging.DebUOS.Tool/Packaging.DebUOS.Tool.csproj b/DebUOS/Packaging.DebUOS.Tool/Packaging.DebUOS.Tool.csproj index 27c03cb..8cd1c49 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Packaging.DebUOS.Tool.csproj +++ b/DebUOS/Packaging.DebUOS.Tool/Packaging.DebUOS.Tool.csproj @@ -7,7 +7,6 @@ enable - false true @@ -19,12 +18,9 @@ true true + True + dotnet-dpkg-debuos - - - - - diff --git a/DebUOS/Packaging.DebUOS.Tool/Program.cs b/DebUOS/Packaging.DebUOS.Tool/Program.cs index 6332f08..370b42f 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Program.cs +++ b/DebUOS/Packaging.DebUOS.Tool/Program.cs @@ -8,7 +8,6 @@ using Packaging.DebUOS; using Packaging.DebUOS.Contexts.Configurations; using Packaging.DebUOS.Tool; -using Packing.DebUOS; var options = CommandLine.Parse(args).As(); diff --git a/DotNETBuild.sln b/DotNETBuild.sln index 006a4a7..3e4fd9e 100644 --- a/DotNETBuild.sln +++ b/DotNETBuild.sln @@ -97,6 +97,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Packaging.DebUOS", "DebUOS\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Packaging.DebUOS.Tool", "DebUOS\Packaging.DebUOS.Tool\Packaging.DebUOS.Tool.csproj", "{A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Packaging.DebUOS.NuGet", "DebUOS\Packaging.DebUOS.NuGet\Packaging.DebUOS.NuGet.csproj", "{977A7109-C37B-4CFC-875B-77E7149EFCCF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -515,6 +517,18 @@ Global {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Release|x64.Build.0 = Release|Any CPU {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Release|x86.ActiveCfg = Release|Any CPU {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD}.Release|x86.Build.0 = Release|Any CPU + {977A7109-C37B-4CFC-875B-77E7149EFCCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {977A7109-C37B-4CFC-875B-77E7149EFCCF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {977A7109-C37B-4CFC-875B-77E7149EFCCF}.Debug|x64.ActiveCfg = Debug|Any CPU + {977A7109-C37B-4CFC-875B-77E7149EFCCF}.Debug|x64.Build.0 = Debug|Any CPU + {977A7109-C37B-4CFC-875B-77E7149EFCCF}.Debug|x86.ActiveCfg = Debug|Any CPU + {977A7109-C37B-4CFC-875B-77E7149EFCCF}.Debug|x86.Build.0 = Debug|Any CPU + {977A7109-C37B-4CFC-875B-77E7149EFCCF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {977A7109-C37B-4CFC-875B-77E7149EFCCF}.Release|Any CPU.Build.0 = Release|Any CPU + {977A7109-C37B-4CFC-875B-77E7149EFCCF}.Release|x64.ActiveCfg = Release|Any CPU + {977A7109-C37B-4CFC-875B-77E7149EFCCF}.Release|x64.Build.0 = Release|Any CPU + {977A7109-C37B-4CFC-875B-77E7149EFCCF}.Release|x86.ActiveCfg = Release|Any CPU + {977A7109-C37B-4CFC-875B-77E7149EFCCF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -538,6 +552,7 @@ Global {C69F9A99-8110-4379-A22C-176B3E05E481} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} {125615F2-0873-4C66-8FB3-E334D2F5BE9C} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} {A5FDA79F-411A-4AC4-8B59-DA7E122ABAAD} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} + {977A7109-C37B-4CFC-875B-77E7149EFCCF} = {AC990428-ACB7-46A9-B66A-AF0557A7D0C6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A6E377C6-EDE0-4135-951F-78C62B63DE96} From 2d061df828777f8aab4557262e29a1f4559f2c81 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 14:37:16 +0800 Subject: [PATCH 50/74] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=E6=89=93=E5=8C=85=E6=97=A5=E5=BF=97=E4=BF=AE=E5=A4=8D=E4=B9=B1?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.Tool/Program.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/DebUOS/Packaging.DebUOS.Tool/Program.cs b/DebUOS/Packaging.DebUOS.Tool/Program.cs index 370b42f..aef5f78 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Program.cs +++ b/DebUOS/Packaging.DebUOS.Tool/Program.cs @@ -1,5 +1,6 @@ // See https://aka.ms/new-console-template for more information +using System.Text; using dotnetCampus.Cli; using dotnetCampus.Configurations; using dotnetCampus.Configurations.Core; @@ -11,6 +12,8 @@ var options = CommandLine.Parse(args).As(); +Console.OutputEncoding = Encoding.UTF8; + var loggerFactory = LoggerFactory.Create(builder => { builder.AddSimpleConsole(simpleConsoleFormatterOptions => @@ -35,6 +38,13 @@ } else if (!string.IsNullOrEmpty(options.PackageArgumentFilePath)) { + logger.LogInformation($"开始根据配置创建 UOS 的 deb 包。配置文件:{options.PackageArgumentFilePath}"); + if (!File.Exists(options.PackageArgumentFilePath)) + { + logger.LogError($"配置文件 '{options.PackageArgumentFilePath}' 不创建"); + return; + } + var fileConfigurationRepo = ConfigurationFactory.FromFile(options.PackageArgumentFilePath, RepoSyncingBehavior.Static); var appConfigurator = fileConfigurationRepo.CreateAppConfigurator(); var configuration = appConfigurator.Of(); From a2d43410897e913e630f09d0ca88b545b8240b96 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 14:37:59 +0800 Subject: [PATCH 51/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=89=BE=E4=B8=8D?= =?UTF-8?q?=E5=88=B0=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.NuGet/Build/package.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets index 1f4c5d9..73b150e 100644 --- a/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets +++ b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets @@ -245,6 +245,6 @@ - + \ No newline at end of file From 595c7b67b85ac585a4ba92e65c70f364affe509e Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 14:42:17 +0800 Subject: [PATCH 52/74] =?UTF-8?q?=E9=87=8D=E6=96=B0=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E4=B9=B1=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.Tool/Program.cs | 2 -- build/Version.props | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/DebUOS/Packaging.DebUOS.Tool/Program.cs b/DebUOS/Packaging.DebUOS.Tool/Program.cs index aef5f78..84f2158 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Program.cs +++ b/DebUOS/Packaging.DebUOS.Tool/Program.cs @@ -12,8 +12,6 @@ var options = CommandLine.Parse(args).As(); -Console.OutputEncoding = Encoding.UTF8; - var loggerFactory = LoggerFactory.Create(builder => { builder.AddSimpleConsole(simpleConsoleFormatterOptions => diff --git a/build/Version.props b/build/Version.props index 94933d3..3566aee 100644 --- a/build/Version.props +++ b/build/Version.props @@ -1,5 +1,5 @@ - 1.2.1-alpha17 + 1.2.1-alpha21 \ No newline at end of file From 54c9960fb4bea879d7472cfeedc1bce6a6d883ca Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 14:54:03 +0800 Subject: [PATCH 53/74] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E5=8A=A0=E4=B8=8A?= =?UTF-8?q?=E6=A0=87=E5=87=86=E8=BF=87=E6=BB=A4=EF=BC=8C=E4=BD=86=E6=98=AF?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.Tool/Program.cs | 84 +++++++++++++------------ 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/DebUOS/Packaging.DebUOS.Tool/Program.cs b/DebUOS/Packaging.DebUOS.Tool/Program.cs index 84f2158..f3efa6b 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Program.cs +++ b/DebUOS/Packaging.DebUOS.Tool/Program.cs @@ -2,6 +2,7 @@ using System.Text; using dotnetCampus.Cli; +using dotnetCampus.Cli.Standard; using dotnetCampus.Configurations; using dotnetCampus.Configurations.Core; using Microsoft.Extensions.Logging; @@ -10,54 +11,57 @@ using Packaging.DebUOS.Contexts.Configurations; using Packaging.DebUOS.Tool; -var options = CommandLine.Parse(args).As(); - -var loggerFactory = LoggerFactory.Create(builder => +CommandLine.Parse(args).AddStandardHandlers().AddHandler((Options options) => { - builder.AddSimpleConsole(simpleConsoleFormatterOptions => + var loggerFactory = LoggerFactory.Create(builder => { - simpleConsoleFormatterOptions.ColorBehavior = LoggerColorBehavior.Disabled; - simpleConsoleFormatterOptions.SingleLine = true; + builder.AddSimpleConsole(simpleConsoleFormatterOptions => + { + simpleConsoleFormatterOptions.ColorBehavior = LoggerColorBehavior.Disabled; + simpleConsoleFormatterOptions.SingleLine = true; + }); }); -}); -var logger = loggerFactory.CreateLogger(""); + var logger = loggerFactory.CreateLogger(""); -if (!string.IsNullOrEmpty(options.BuildPath)) -{ - var packingFolder = new DirectoryInfo(options.BuildPath); - var outputPath = options.OutputPath ?? Path.Join(packingFolder.FullName,$"{packingFolder.Name}.deb"); - var outputDebFile = new FileInfo(outputPath); - - var debUosPackageCreator = new DebUOSPackageCreator(logger); - //var packingFolder = new DirectoryInfo(@"C:\lindexi\Work\"); - //var outputDebFile = new FileInfo(@"C:\lindexi\Work\Downloader.deb"); - debUosPackageCreator.PackageDeb(packingFolder, outputDebFile); -} -else if (!string.IsNullOrEmpty(options.PackageArgumentFilePath)) -{ - logger.LogInformation($"开始根据配置创建 UOS 的 deb 包。配置文件:{options.PackageArgumentFilePath}"); - if (!File.Exists(options.PackageArgumentFilePath)) + if (!string.IsNullOrEmpty(options.BuildPath)) { - logger.LogError($"配置文件 '{options.PackageArgumentFilePath}' 不创建"); - return; + var packingFolder = new DirectoryInfo(options.BuildPath); + var outputPath = options.OutputPath ?? Path.Join(packingFolder.FullName, $"{packingFolder.Name}.deb"); + var outputDebFile = new FileInfo(outputPath); + + var debUosPackageCreator = new DebUOSPackageCreator(logger); + //var packingFolder = new DirectoryInfo(@"C:\lindexi\Work\"); + //var outputDebFile = new FileInfo(@"C:\lindexi\Work\Downloader.deb"); + debUosPackageCreator.PackageDeb(packingFolder, outputDebFile); } + else if (!string.IsNullOrEmpty(options.PackageArgumentFilePath)) + { + logger.LogInformation($"开始根据配置创建 UOS 的 deb 包。配置文件:{options.PackageArgumentFilePath}"); + if (!File.Exists(options.PackageArgumentFilePath)) + { + logger.LogError($"配置文件 '{options.PackageArgumentFilePath}' 不创建"); + return; + } - var fileConfigurationRepo = ConfigurationFactory.FromFile(options.PackageArgumentFilePath, RepoSyncingBehavior.Static); - var appConfigurator = fileConfigurationRepo.CreateAppConfigurator(); - var configuration = appConfigurator.Of(); + var fileConfigurationRepo = ConfigurationFactory.FromFile(options.PackageArgumentFilePath, RepoSyncingBehavior.Static); + var appConfigurator = fileConfigurationRepo.CreateAppConfigurator(); + var configuration = appConfigurator.Of(); - var fileStructCreator = new DebUOSPackageFileStructCreator(logger); - fileStructCreator.CreatePackagingFolder(configuration); + var fileStructCreator = new DebUOSPackageFileStructCreator(logger); + fileStructCreator.CreatePackagingFolder(configuration); + + var packingFolder = new DirectoryInfo(configuration.PackingFolder!); + var outputDebFile = new FileInfo(configuration.DebUOSOutputFilePath!); + var workingFolder = new DirectoryInfo(configuration.WorkingFolder!); + + var debUosPackageCreator = new DebUOSPackageCreator(logger); + debUosPackageCreator.PackageDeb(packingFolder, outputDebFile, workingFolder); + } + else + { + // Show Help + } +}).Run(); - var packingFolder = new DirectoryInfo(configuration.PackingFolder!); - var outputDebFile = new FileInfo(configuration.DebUOSOutputFilePath!); - var workingFolder = new DirectoryInfo(configuration.WorkingFolder!); - var debUosPackageCreator = new DebUOSPackageCreator(logger); - debUosPackageCreator.PackageDeb(packingFolder, outputDebFile,workingFolder); -} -else -{ - // Show Help -} From 6e59862a918bfd1058e28c46d01bda3006260e60 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 14:54:11 +0800 Subject: [PATCH 54/74] =?UTF-8?q?Revert=20"=E5=B0=9D=E8=AF=95=E5=8A=A0?= =?UTF-8?q?=E4=B8=8A=E6=A0=87=E5=87=86=E8=BF=87=E6=BB=A4=EF=BC=8C=E4=BD=86?= =?UTF-8?q?=E6=98=AF=E5=A4=B1=E8=B4=A5"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 54c9960fb4bea879d7472cfeedc1bce6a6d883ca. --- DebUOS/Packaging.DebUOS.Tool/Program.cs | 84 ++++++++++++------------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/DebUOS/Packaging.DebUOS.Tool/Program.cs b/DebUOS/Packaging.DebUOS.Tool/Program.cs index f3efa6b..84f2158 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Program.cs +++ b/DebUOS/Packaging.DebUOS.Tool/Program.cs @@ -2,7 +2,6 @@ using System.Text; using dotnetCampus.Cli; -using dotnetCampus.Cli.Standard; using dotnetCampus.Configurations; using dotnetCampus.Configurations.Core; using Microsoft.Extensions.Logging; @@ -11,57 +10,54 @@ using Packaging.DebUOS.Contexts.Configurations; using Packaging.DebUOS.Tool; -CommandLine.Parse(args).AddStandardHandlers().AddHandler((Options options) => +var options = CommandLine.Parse(args).As(); + +var loggerFactory = LoggerFactory.Create(builder => { - var loggerFactory = LoggerFactory.Create(builder => + builder.AddSimpleConsole(simpleConsoleFormatterOptions => { - builder.AddSimpleConsole(simpleConsoleFormatterOptions => - { - simpleConsoleFormatterOptions.ColorBehavior = LoggerColorBehavior.Disabled; - simpleConsoleFormatterOptions.SingleLine = true; - }); + simpleConsoleFormatterOptions.ColorBehavior = LoggerColorBehavior.Disabled; + simpleConsoleFormatterOptions.SingleLine = true; }); +}); - var logger = loggerFactory.CreateLogger(""); +var logger = loggerFactory.CreateLogger(""); - if (!string.IsNullOrEmpty(options.BuildPath)) +if (!string.IsNullOrEmpty(options.BuildPath)) +{ + var packingFolder = new DirectoryInfo(options.BuildPath); + var outputPath = options.OutputPath ?? Path.Join(packingFolder.FullName,$"{packingFolder.Name}.deb"); + var outputDebFile = new FileInfo(outputPath); + + var debUosPackageCreator = new DebUOSPackageCreator(logger); + //var packingFolder = new DirectoryInfo(@"C:\lindexi\Work\"); + //var outputDebFile = new FileInfo(@"C:\lindexi\Work\Downloader.deb"); + debUosPackageCreator.PackageDeb(packingFolder, outputDebFile); +} +else if (!string.IsNullOrEmpty(options.PackageArgumentFilePath)) +{ + logger.LogInformation($"开始根据配置创建 UOS 的 deb 包。配置文件:{options.PackageArgumentFilePath}"); + if (!File.Exists(options.PackageArgumentFilePath)) { - var packingFolder = new DirectoryInfo(options.BuildPath); - var outputPath = options.OutputPath ?? Path.Join(packingFolder.FullName, $"{packingFolder.Name}.deb"); - var outputDebFile = new FileInfo(outputPath); - - var debUosPackageCreator = new DebUOSPackageCreator(logger); - //var packingFolder = new DirectoryInfo(@"C:\lindexi\Work\"); - //var outputDebFile = new FileInfo(@"C:\lindexi\Work\Downloader.deb"); - debUosPackageCreator.PackageDeb(packingFolder, outputDebFile); + logger.LogError($"配置文件 '{options.PackageArgumentFilePath}' 不创建"); + return; } - else if (!string.IsNullOrEmpty(options.PackageArgumentFilePath)) - { - logger.LogInformation($"开始根据配置创建 UOS 的 deb 包。配置文件:{options.PackageArgumentFilePath}"); - if (!File.Exists(options.PackageArgumentFilePath)) - { - logger.LogError($"配置文件 '{options.PackageArgumentFilePath}' 不创建"); - return; - } - var fileConfigurationRepo = ConfigurationFactory.FromFile(options.PackageArgumentFilePath, RepoSyncingBehavior.Static); - var appConfigurator = fileConfigurationRepo.CreateAppConfigurator(); - var configuration = appConfigurator.Of(); + var fileConfigurationRepo = ConfigurationFactory.FromFile(options.PackageArgumentFilePath, RepoSyncingBehavior.Static); + var appConfigurator = fileConfigurationRepo.CreateAppConfigurator(); + var configuration = appConfigurator.Of(); - var fileStructCreator = new DebUOSPackageFileStructCreator(logger); - fileStructCreator.CreatePackagingFolder(configuration); - - var packingFolder = new DirectoryInfo(configuration.PackingFolder!); - var outputDebFile = new FileInfo(configuration.DebUOSOutputFilePath!); - var workingFolder = new DirectoryInfo(configuration.WorkingFolder!); - - var debUosPackageCreator = new DebUOSPackageCreator(logger); - debUosPackageCreator.PackageDeb(packingFolder, outputDebFile, workingFolder); - } - else - { - // Show Help - } -}).Run(); + var fileStructCreator = new DebUOSPackageFileStructCreator(logger); + fileStructCreator.CreatePackagingFolder(configuration); + var packingFolder = new DirectoryInfo(configuration.PackingFolder!); + var outputDebFile = new FileInfo(configuration.DebUOSOutputFilePath!); + var workingFolder = new DirectoryInfo(configuration.WorkingFolder!); + var debUosPackageCreator = new DebUOSPackageCreator(logger); + debUosPackageCreator.PackageDeb(packingFolder, outputDebFile,workingFolder); +} +else +{ + // Show Help +} From 1cbce682587e2c943217b003a27458411299b098 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 15:08:41 +0800 Subject: [PATCH 55/74] =?UTF-8?q?=E5=B8=A6=E4=B8=8A=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E5=B8=AE=E5=8A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.Tool/Options.cs | 6 +++--- DebUOS/Packaging.DebUOS.Tool/Program.cs | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/DebUOS/Packaging.DebUOS.Tool/Options.cs b/DebUOS/Packaging.DebUOS.Tool/Options.cs index dd2d385..a45d0d0 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Options.cs +++ b/DebUOS/Packaging.DebUOS.Tool/Options.cs @@ -11,15 +11,15 @@ public class Options /// 将给定路径文件夹打包为 UOS 的 deb 包 /// /// 和 二选一,如果同时存在,优先使用 参数 - [Option('b', "Build", Description = "Build path")] + [Option('b', "Build", Description = "Build path", LocalizableDescription = "将符合 UOS 安装包组织规范的文件夹打包为 deb 包,和 -p 参数二选一")] public string? BuildPath { set; get; } /// /// 将根据给定的打包参数文件打包为 UOS 的 deb 包 /// - [Option('p', "Pack", Description = "Package argument file path")] + [Option('p', "Pack", Description = "Package argument file path", LocalizableDescription = "使用给定的 coin 格式参数文件制作 deb 包")] public string? PackageArgumentFilePath { set; get; } - [Option('o', "Output", Description = "Output path")] + [Option('o', "Output", Description = "Output path", LocalizableDescription = "输出的 deb 文件路径")] public string? OutputPath { set; get; } } diff --git a/DebUOS/Packaging.DebUOS.Tool/Program.cs b/DebUOS/Packaging.DebUOS.Tool/Program.cs index 84f2158..344c688 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Program.cs +++ b/DebUOS/Packaging.DebUOS.Tool/Program.cs @@ -1,5 +1,6 @@ // See https://aka.ms/new-console-template for more information +using System.Reflection; using System.Text; using dotnetCampus.Cli; using dotnetCampus.Configurations; @@ -60,4 +61,16 @@ else { // Show Help + var stringBuilder = new StringBuilder() + .AppendLine($"用法:[options] [arguments]"); + foreach (var propertyInfo in typeof(Options).GetProperties()) + { + var optionAttribute = propertyInfo.GetCustomAttribute(); + if (optionAttribute != null) + { + stringBuilder.AppendLine($"-{optionAttribute.ShortName} {(optionAttribute.LongName ?? string.Empty).PadRight(10)} {optionAttribute.Description} {optionAttribute.LocalizableDescription}"); + } + } + + Console.WriteLine(stringBuilder.ToString()); } From 33d6f6b03a964f1ae9c60bce865ce1c51699850d Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 15:35:17 +0800 Subject: [PATCH 56/74] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=B8=8D=E5=B8=A6?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=91=BD=E4=BB=A4=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.Tool/Properties/launchSettings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DebUOS/Packaging.DebUOS.Tool/Properties/launchSettings.json b/DebUOS/Packaging.DebUOS.Tool/Properties/launchSettings.json index 47e7f7c..0b8bdee 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Properties/launchSettings.json +++ b/DebUOS/Packaging.DebUOS.Tool/Properties/launchSettings.json @@ -1,8 +1,7 @@ { "profiles": { "Packing.DebUOS.Tool": { - "commandName": "Project", - "commandLineArgs": "-p C:\\lindexi\\Work\\DebUOSPackingArgs.coin" + "commandName": "Project" } } } \ No newline at end of file From 98c58a37c9b5858e96c1c0f021d59904918190d3 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 15:35:23 +0800 Subject: [PATCH 57/74] =?UTF-8?q?Revert=20"=E6=B5=8B=E8=AF=95=E4=B8=8D?= =?UTF-8?q?=E5=B8=A6=E5=8F=82=E6=95=B0=E5=91=BD=E4=BB=A4=E8=A1=8C"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 33d6f6b03a964f1ae9c60bce865ce1c51699850d. --- DebUOS/Packaging.DebUOS.Tool/Properties/launchSettings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DebUOS/Packaging.DebUOS.Tool/Properties/launchSettings.json b/DebUOS/Packaging.DebUOS.Tool/Properties/launchSettings.json index 0b8bdee..47e7f7c 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Properties/launchSettings.json +++ b/DebUOS/Packaging.DebUOS.Tool/Properties/launchSettings.json @@ -1,7 +1,8 @@ { "profiles": { "Packing.DebUOS.Tool": { - "commandName": "Project" + "commandName": "Project", + "commandLineArgs": "-p C:\\lindexi\\Work\\DebUOSPackingArgs.coin" } } } \ No newline at end of file From fb8846c8db81621322d07ebdf90f46a28794f8f7 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 15:52:10 +0800 Subject: [PATCH 58/74] =?UTF-8?q?=E6=89=93=E5=8C=85=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs | 4 +++- build/Version.props | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs index 07c6479..af65963 100644 --- a/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs @@ -26,7 +26,7 @@ public DebUOSPackageCreator(ILogger logger) public void PackageDeb(DirectoryInfo packingFolder, FileInfo outputDebFile, DirectoryInfo? workingFolder = null) { - Logger.LogInformation($"Start packing UOS deb from '{packingFolder.FullName}' to '{outputDebFile.FullName}'"); + Logger.LogInformation($"开始打包。Start packaging UOS deb from '{packingFolder.FullName}' to '{outputDebFile.FullName}'"); ArchiveBuilder archiveBuilder = new ArchiveBuilder() { @@ -77,6 +77,8 @@ public void PackageDeb(DirectoryInfo packingFolder, FileInfo outputDebFile, Dire ArFileCreator.WriteEntry(targetStream, "data.tar.xz", ArFileMode, tarXzStream); } } + + Logger.LogInformation($"打包完成 '{outputDebFile.FullName}'"); } private void WriteControl(DirectoryInfo packingFolder, Stream targetStream) diff --git a/build/Version.props b/build/Version.props index 3566aee..bc99561 100644 --- a/build/Version.props +++ b/build/Version.props @@ -1,5 +1,5 @@ - 1.2.1-alpha21 + 1.2.1-alpha22 \ No newline at end of file From 188b0bd4f70525e4eb257d54cbe927975b413048 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 17:13:35 +0800 Subject: [PATCH 59/74] =?UTF-8?q?=E6=8F=90=E4=BE=9B=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=AE=9A=E5=88=B6=E5=AE=89=E8=A3=85=E5=8C=85=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.NuGet/Build/package.targets | 5 +++++ .../Contexts/Configurations/DebUOSConfiguration.cs | 6 ++++++ DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs | 4 ++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets index 73b150e..d4bc902 100644 --- a/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets +++ b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets @@ -68,6 +68,11 @@ + + + + + diff --git a/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs index 18b53bf..de2624a 100644 --- a/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs +++ b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs @@ -53,6 +53,12 @@ public string Version get => GetString() ?? "1.0.0"; } + public string UOSDebVersion + { + set => SetValue(value); + get => GetString() ?? Version; + } + public string DebControlSection { set => SetValue(value); diff --git a/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs b/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs index 525076a..37d2df2 100644 --- a/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs +++ b/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs @@ -209,7 +209,7 @@ public void CreatePackagingFolder(DebUOSConfiguration configuration) { AppId = appId, ApplicationName = configuration.AppName, - Version = configuration.Version, + Version = configuration.UOSDebVersion, Architecture = configuration.Architecture.Split(';') }; @@ -273,7 +273,7 @@ public void CreatePackagingFolder(DebUOSConfiguration configuration) var stringBuilder = new StringBuilder(); stringBuilder .Append($"Package: {appId}\n") - .Append($"Version: {configuration.Version}\n") + .Append($"Version: {configuration.UOSDebVersion}\n") .Append($"Section: {configuration.DebControlSection}\n") .Append($"Priority: {configuration.DebControlPriority}\n") .Append($"Architecture: {configuration.Architecture}\n") From 04bae2740583d30ee7aea0bc7503c887b56972de Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 17:19:14 +0800 Subject: [PATCH 60/74] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 DebUOS/README.md diff --git a/DebUOS/README.md b/DebUOS/README.md new file mode 100644 index 0000000..a7c7bdd --- /dev/null +++ b/DebUOS/README.md @@ -0,0 +1,56 @@ +# 制作符合 UOS 规范的 deb 安装包的工具 + +## 使用方法 + +有以下两个使用方式,任选其一即可 + +### 命令行工具方式 + +使用以下命令进行更新或安装工具: + +``` +dotnet tool update -g Packaging.DebUOS.Tool +``` + +将已经准备好的符合 UOS 安装包文件组织规范的文件夹打包为 deb 安装包: + +``` +dotnet dpkg-debuos -b C:\lindexi\DebPacking -o C:\lindexi\UOS\Foo.deb +``` + +以上的 `C:\lindexi\DebPacking` 为已准备好的符合 UOS 安装包文件组织规范的文件夹,以上的 `C:\lindexi\UOS\Foo.deb` 为打包输出的文件。其中 `-o` 指定打包输出文件参数可以忽略,如忽略此参数,则将会在打包文件夹输出 deb 安装包 + +使用命令行工具比较适合创建构建更为复杂的 deb 安装包,可以有更强的定制化,适合对 UOS 安装包规范较熟悉的开发者使用。或者是作为 `dpkg-deb` 工具在 Windows 上的替代品 + +### 配合 csproj 的 NuGet 包方式 + +请通过 NuGet 管理器或采用如下代码编辑 csproj 文件安装 `Packaging.DebUOS` 库 + +```xml + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + +``` + +接着添加一些必要的配置参数,比如用来配置 UOS 的 AppId 的 `UOSAppId` 属性,如以下代码示例。更多的属性配置请参阅下文 + +```xml + + com.xx.xxx + +``` + +通过如下命令行发布时,即可打出符合 UOS 规范的 deb 包 + +``` +dotnet publish -t:CreateDebUOS -c release -r linux-x64 --self-contained +``` + +以上命令行与传统的发布命令最大的不同在于添加了 `-t:CreateDebUOS` 参数,通过此参数即可触发名为 `CreateDebUOS` 的 Target 进行创建 deb 包 + +通过 NuGet 包配置的方法,可以很方便进行接入,且不需要有许多额外的知识。可以完全复用原有的构建工具链,可以配合其他工具实现一次打包创建多个平台的安装包,可以将各项配置写入到 csproj 里面方便客制化定制以及接入更多自动化参数和加入代码管理 + +更多可配置属性请参阅 DebUOSConfiguration.cs 文件 \ No newline at end of file From b22e4f08d252b7f6198783992e150d1fb71e1fb4 Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 17:59:38 +0800 Subject: [PATCH 61/74] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Configurations/DebUOSConfiguration.cs | 115 ++++++++++++++++-- 1 file changed, 104 insertions(+), 11 deletions(-) diff --git a/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs index de2624a..0b15b28 100644 --- a/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs +++ b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs @@ -17,180 +17,273 @@ public string? AssemblyName get => GetString(); } + /// + /// 自定义的 DEBIAN\control 文件路径,将直接使用该文件作为 control 文件,忽略其他配置。这是比较高级的配置,一般不需要使用,可以用来满足更多的定制化需求 + /// public string? DebControlFile { set => SetValue(value); get => GetString(); } + /// + /// 自定义的 opt\apps\${AppId}\info 文件路径,将直接使用该文件作为 info 文件,忽略其他配置。这是比较高级的配置,一般不需要使用,可以用来满足更多的定制化需求 + /// public string? DebInfoFile { set => SetValue(value); get => GetString(); } + /// + /// 自定义的 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件路径,将直接使用该文件作为 desktop 文件,忽略其他配置。这是比较高级的配置,一般不需要使用,可以用来满足更多的定制化需求 + /// public string? DebDesktopFile { set => SetValue(value); get => GetString(); } + /// + /// 应用的 AppId 值,用来组织应用的安装路径,同时也是应用的唯一标识。按照 UOS 的规范,请务必使用厂商的倒置域名+产品名作为应用包名,如 `com.example.demo` 格式,前半部分为厂商域名倒置,后半部分为产品名,如果使用非拥有者的域名作为前缀,可能会引起该域名拥有者进行申诉,导致软件被申诉下架或者删除,只允许小写字母。不写默认和 AssemblyName 属性相同 + /// public string? AppId { set => SetValue(value); - get => GetString()??AssemblyName; + get => GetString() ?? AssemblyName; } + /// + /// 应用的 AppId 值,用来组织应用的安装路径,同时也是应用的唯一标识。按照 UOS 的规范,请务必使用厂商的倒置域名+产品名作为应用包名,如 `com.example.demo` 格式,前半部分为厂商域名倒置,后半部分为产品名,如果使用非拥有者的域名作为前缀,可能会引起该域名拥有者进行申诉,导致软件被申诉下架或者删除,只允许小写字母。不写默认和 AppId 属性相同 + /// + /// 与 属性不同的是,该属性明确给制作 UOS 的包使用,不会和其他的逻辑的 AppId 混淆 + /// public string? UOSAppId { set => SetValue(value); get => GetString() ?? AppId; } + /// + /// 版本号,默认是 1.0.0 版本 + /// public string Version { set => SetValue(value); get => GetString() ?? "1.0.0"; } + /// + /// 专门给制作 UOS 的包使用的版本号,不写将使用 属性的值。可使用 a.b.c 格式,也可以比较复杂的语义版本号格式,如 `1.2.3-2+b1` 等格式 + /// public string UOSDebVersion { set => SetValue(value); get => GetString() ?? Version; } + /// + /// 配置放入到 DEBIAN\control 文件的 Section 属性,可以选用 utils,admin, devel, doc, libs, net, 或者 unknown 等等,代表着该软件包在 Debian 仓库中将被归属到什么样的逻辑子分类中。默认是 utils 类型 + /// public string DebControlSection { set => SetValue(value); get => GetString() ?? "utils"; } + /// + /// 配置放入到 DEBIAN\control 文件的 Priority 属性,可以选用 required, important, standard, optional, extra 等等,代表着该软件包在 Debian 仓库中的优先级,optional 优先级适用于与优先级为 required、important 或 standard 的软件包不冲突的新软件包。也可以做其它取值。若是不明了,请使用 optional。默认是 optional 类型 + /// public string DebControlPriority { set => SetValue(value); get => GetString() ?? "optional"; } + /// + /// 配置放入到 DEBIAN\control 文件的 Architecture 属性,以及 opt\apps\${AppId}\info 文件的 arch 属性。可以选用 amd64, i386, arm64, armel, armhf, mips, mips64el, mipsel, ppc64el, s390x, 或者 all 等等,代表着该软件包在 Debian 仓库中的架构,amd64 代表着 64 位的 x86 架构,i386 代表着 32 位的 x86 架构,arm64 代表着 64 位的 ARM 架构,armel 代表着 32 位的 ARM 架构,armhf 代表着 32 位的 ARM 架构,mips 代表着 32 位的 MIPS 架构,mips64el 代表着 64 位的 MIPS 架构,mipsel 代表着 32 位的 MIPS 架构,ppc64el 代表着 64 位的 PowerPC 架构,s390x 代表着 64 位的 IBM S/390 架构,all 代表着所有架构。目前商店支持以下的 amd64, mips64el, arm64, sw_64, loongarch64 几种架构。默认是 amd64 类型 + /// public string Architecture { set => SetValue(value); get => GetString() ?? "amd64"; } + /// + /// 配置放入到 DEBIAN\control 文件的 Multi-Arch 属性。默认是 foreign 类型 + /// public string DebControlMultiArch { set => SetValue(value); get => GetString() ?? "foreign"; } + /// + /// 配置放入到 DEBIAN\control 文件的 Build-Depends 属性。默认是 debhelper (>=9) 类型 + /// public string DebControlBuildDepends { set => SetValue(value); get => GetString() ?? "debhelper (>=9)"; } + /// + /// 配置放入到 DEBIAN\control 文件的 Standards-Version 属性。默认是 3.9.6 的值 + /// public string DebControlStandardsVersion { set => SetValue(value); get => GetString() ?? "3.9.6"; } + /// + /// 配置放入到 DEBIAN\control 文件的 Maintainer 属性。如不填写,默认将会按照 Authors Author Company Publisher 的顺序,找到第一个不为空的值,作为 Maintainer 的值。如最终依然为空,可能导致打出来的安装包在用户端安装之后,不能在开始菜单中找到应用的图标 + /// public string? DebControlMaintainer { set => SetValue(value); get => GetString(); } + /// + /// 配置放入到 DEBIAN\control 文件的 Homepage 属性。如不填写,将尝试使用 PackageProjectUrl 属性,如依然为空则采用默认值。默认是 https://www.uniontech.com 的值 + /// public string DebControlHomepage { set => SetValue(value); get => GetString() ?? "https://www.uniontech.com"; } + /// + /// 配置放入到 DEBIAN\control 文件的 Description 属性。如不填写,默认将使用 Description 属性的值 + /// public string? DebControlDescription { set => SetValue(value); get => GetString(); } + /// + /// 应用名,英文名。将作为 opt\apps\${AppId}\entries\applications\${AppId}.desktop 和 opt\apps\${AppId}\info 的 Name 属性的值,不写默认和 AssemblyName 属性相同 + /// public string? AppName { set => SetValue(value); get => GetString() ?? AssemblyName; } - public string? InfoPermissions + /// + /// 应用名,中文名,可不写。将在开始菜单中显示 + /// + public string? AppNameZhCN { set => SetValue(value); get => GetString(); } - public string? AppNameZhCN + /// + /// 配置放入到 opt\apps\${AppId}\info 文件的 permissions 属性,可不写,可开启的属性之间使用分号 ; 分割。可选值有:autostart, notification, trayicon, clipboard, account, bluetooth, camera, audio_record, installed_apps 等。默认为不开启任何权限 + /// + public string? InfoPermissions { set => SetValue(value); get => GetString(); } + + /// + /// 配置放入到 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件的 Categories 属性,可选值有:AudioVideo, Audio, Video, Development, Education, Game, Graphics, Network, Office, Science, Settings, System, Utility, Other 等。默认为 Other 的值 + /// public string DesktopCategories { set => SetValue(value); get => GetString() ?? "Other"; } + /// + /// 配置放入到 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件的 Keywords 属性,作为程序的通用关键搜索词,当在启动器中搜索该词而非程序名称时,即可索引出该程序的快捷方式。多个关键词之间使用分号 ; 分割,关键词使用英文。如需添加中文关键词,请设置 属性。默认为 deepin 的值 + /// public string DesktopKeywords { set => SetValue(value); - get => GetString()?? "deepin"; + get => GetString() ?? "deepin"; } + /// + /// 配置放入到 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件的 Keywords[zh_CN] 属性,可不填,作为程序的通用关键搜索词,当在启动器中搜索该词而非程序名称时,即可索引出该程序的快捷方式。多个关键词之间使用分号 ; 分割,关键词使用中文 + /// public string? DesktopKeywordsZhCN { set => SetValue(value); get => GetString(); } + /// + /// 配置放入到 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件的 Comment 属性,作为关于本程序的通用简述,在没有单独设置语言参数的情况下,默认显示该段内容。不填将使用 属性的值 + /// public string DesktopComment { set => SetValue(value); get => GetString() ?? UOSAppId; } + /// + /// 配置放入到 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件的 Comment[zh_CN] 属性,作为关于本程序的通用中文简述,可不填 + /// public string? DesktopCommentZhCN { set => SetValue(value); get => GetString(); } + /// + /// 配置放入到 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件的 Exec 属性,作为程序的启动命令,可不填,且推荐不填,除非有特殊需求。默认为 /opt/apps/${AppId}/files/bin/${AssemblyName} 的值 + /// public string? DesktopExec { set => SetValue(value); get => GetString(); } + /// + /// 配置放入到 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件的 Icon 属性,作为程序的图标,可不填,且推荐不填,除非有特殊需求。默认为 的值 + /// public string? DesktopIcon { set => SetValue(value); get => GetString(); } + /// + /// 配置放入到 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件的 Type 属性,作为程序的类型,按照 UOS 的规范,必须为 Application 的值,推荐不更改,即不填 + /// public string DesktopType { set => SetValue(value); get => GetString() ?? "Application"; } + /// + /// 配置放入到 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件的 Terminal 属性,用来决定程序是否以终端的形式运行,默认是 false 关闭状态 + /// public bool DesktopTerminal { set => SetValue(value); get => GetBoolean() ?? false; } + /// + /// 配置放入到 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件的 StartupNotify 属性,用来决定程序是否允许桌面环境跟踪应用程序的启动,提供用户反馈和其他功能。例如鼠标的等待动画等,按照 UOS 规范建议,为保障应用使用体验,默认是 true 开启状态,推荐不更改,即不填 + /// public bool DesktopStartupNotify { set => SetValue(value); get => GetBoolean() ?? true; } + /// + /// 配置放入到 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件的 MimeType 属性,用来配置程序支持的关联文件类型,根据实际需求来填写。如果没有需要支持关联文件,则不填。多个文件类型之间使用分号 ; 分割 + /// public string? DesktopMimeType { set => SetValue(value); @@ -243,7 +336,7 @@ public string? DebUOSOutputFilePath { name = Path.GetDirectoryName(ProjectPublishFolder); } - + return Path.Join(ProjectPublishFolder, $"{name}.deb"); } } @@ -252,11 +345,11 @@ public string? DebUOSOutputFilePath } } - public string? IconFolder - { - set => SetValue(value); - get => GetString(); - } + //public string? IconFolder + //{ + // set => SetValue(value); + // get => GetString(); + //} public string? SvgIconFile { @@ -305,4 +398,4 @@ public string? Png512x512IconFile set => SetValue(value); get => GetString(); } -} +} \ No newline at end of file From c8f510e930eef69748f5701403ad32868b74527c Mon Sep 17 00:00:00 2001 From: lindexi Date: Wed, 3 Jan 2024 17:59:54 +0800 Subject: [PATCH 62/74] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E5=B8=AE=E5=8A=A9?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/README.md b/README.md index 8b1ac47..35cffb6 100644 --- a/README.md +++ b/README.md @@ -452,6 +452,54 @@ SyncTool sync [参数] - `-a` 或 `-Address` : 同步服务的地址。必填,格式如 `http://127.0.0.1:56621` 等地址 - `-f` 或 `-Folder` : 本地同步的文件夹。可选,不填默认为工作路径 +### 制作符合 UOS 规范的 deb 安装包的工具 + +可以打包出符合 UOS 规范的 deb 安装包的工具 + +提供两个不同的使用方式,分别是命令行方式和配合 csproj 的 NuGet 包方式 + +命令行方式通过 dotnet tool 方式使用,先使用以下命令安装工具 + +``` +dotnet tool update -g Packaging.DebUOS.Tool +``` + +将已经准备好的符合 UOS 安装包文件组织规范的文件夹打包为 deb 安装包: + +``` +dotnet dpkg-debuos -b C:\lindexi\DebPacking -o C:\lindexi\UOS\Foo.deb +``` + +配合 csproj 的 NuGet 包方式: + +请通过 NuGet 管理器或采用如下代码编辑 csproj 文件安装 `Packaging.DebUOS` 库 + +```xml + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + +``` + +接着添加一些必要的配置参数,比如用来配置 UOS 的 AppId 的 `UOSAppId` 属性,如以下代码示例。更多的属性配置请参阅下文 + +```xml + + com.xx.xxx + +``` + +通过如下命令行发布时,即可打出符合 UOS 规范的 deb 包 + +``` +dotnet publish -t:CreateDebUOS -c release -r linux-x64 --self-contained +``` + +更多请参阅 [Packaging.DebUOS 工具](./DebUOS) + + ## 相似的项目 [dotnetcore/FlubuCore: A cross platform build and deployment automation system for building projects and executing deployment scripts using C# code.](https://github.com/dotnetcore/FlubuCore ) From 47c61406589467c646ca74fd995cc2da5cbd68c0 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 4 Jan 2024 14:32:39 +0800 Subject: [PATCH 63/74] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9B=B4=E5=8A=A0?= =?UTF-8?q?=E7=BB=86=E8=8A=82=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contexts/Configurations/DebUOSConfiguration.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs index 0b15b28..cc3c781 100644 --- a/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs +++ b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs @@ -351,48 +351,62 @@ public string? DebUOSOutputFilePath // get => GetString(); //} + /// + /// 应用图标文件,表示矢量图标文件。将被放入到 opt/apps/${AppId}/entries/icons/hicolor/scalable/apps/${appid}.svg 里面。矢量图标文件与 png 非矢量格式二选一,如果同时存在,优先使用矢量图标文件。 + /// 当 属性存在值时,本属性设置无效 + /// public string? SvgIconFile { set => SetValue(value); get => GetString(); } + /// + /// 应用图标文件,表示 png 非矢量格式文件。将被放入到 opt/apps/${AppId}/entries/icons/hicolor/${分辨率}/apps/${appid}.png 里面。请确保实际图片分辨率正确,且是 png 格式。矢量图标文件与 png 非矢量格式二选一,如果同时存在,优先使用矢量图标文件。 + /// 当 属性存在值时,本属性设置无效 + /// public string? Png16x16IconFile { set => SetValue(value); get => GetString(); } + /// public string? Png24x24IconFile { set => SetValue(value); get => GetString(); } + /// public string? Png32x32IconFile { set => SetValue(value); get => GetString(); } + /// public string? Png48x48IconFile { set => SetValue(value); get => GetString(); } + /// public string? Png128x128IconFile { set => SetValue(value); get => GetString(); } + /// public string? Png256x256IconFile { set => SetValue(value); get => GetString(); } + /// public string? Png512x512IconFile { set => SetValue(value); From 1897023c6d36acdd59c56f94aa78fa70cf6dc5d1 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 4 Jan 2024 14:32:52 +0800 Subject: [PATCH 64/74] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/DebUOS/README.md b/DebUOS/README.md index a7c7bdd..60a1299 100644 --- a/DebUOS/README.md +++ b/DebUOS/README.md @@ -51,6 +51,11 @@ dotnet publish -t:CreateDebUOS -c release -r linux-x64 --self-contained 以上命令行与传统的发布命令最大的不同在于添加了 `-t:CreateDebUOS` 参数,通过此参数即可触发名为 `CreateDebUOS` 的 Target 进行创建 deb 包 -通过 NuGet 包配置的方法,可以很方便进行接入,且不需要有许多额外的知识。可以完全复用原有的构建工具链,可以配合其他工具实现一次打包创建多个平台的安装包,可以将各项配置写入到 csproj 里面方便客制化定制以及接入更多自动化参数和加入代码管理 +通过 NuGet 包配置的方法,可以很方便进行接入,自带大量的默认配置,从零开始接入的成本低,且不需要有许多额外的知识。可以完全复用原有的构建工具链,可以配合其他工具实现一次打包创建多个平台的安装包,可以将各项配置写入到 csproj 里面方便客制化定制以及接入更多自动化参数和加入代码管理 -更多可配置属性请参阅 DebUOSConfiguration.cs 文件 \ No newline at end of file +更多可配置属性请参阅 DebUOSConfiguration.cs 文件 + +调试方法: + +- 可通过命令行参数输出了解打包参数的 `DebUOSPackingArgs.coin` 路径,从而了解打包所输入的参数。可以使用将此参数文件重新输入进行调试 +- 可通过命令行参数输出了解文件组织的 `DebUOSPacking\Packing` 文件夹路径,通过此文件夹即可了解打出的 deb 的文件夹内容,也可以切换到 Linux 服务器上使用更稳定的 `dpkg-deb` 对此文件夹进行打包 \ No newline at end of file From b141d16c3427a52ebfebd889a750fd44e1944b75 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 4 Jan 2024 14:49:09 +0800 Subject: [PATCH 65/74] =?UTF-8?q?=E6=8F=90=E4=BE=9B=E7=9F=A2=E9=87=8F?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E6=96=87=E4=BB=B6=E5=A4=B9=E6=8B=B7=E8=B4=9D?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Configurations/DebUOSConfiguration.cs | 20 ++++++++++++------- .../DebUOSPackageFileStructCreator.cs | 20 ++++++++++++++++--- .../Packaging.DebUOS/Packaging.DebUOS.csproj | 2 ++ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs index cc3c781..5f79f1e 100644 --- a/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs +++ b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs @@ -345,15 +345,21 @@ public string? DebUOSOutputFilePath } } - //public string? IconFolder - //{ - // set => SetValue(value); - // get => GetString(); - //} + /// + /// 表示图标文件夹路径,文件夹里面按照 UOS 的 deb 规范组织图标文件,文件夹里面存放的内容将会被原原本本拷贝到 opt/apps/${AppId}/entries/icons/ 文件夹里面。此属性属于高级配置,一般不需要使用,可以用来满足更多的定制化需求。默认不填,且推荐在充分理解 UOS 的 deb 规范的情况下再进行使用。此属性存在值时,将会忽略 等属性的设置 + /// + /// + /// 如果此属性配置不正确或图标文件夹的组织不正确,将会导致安装完成之后,无法从开始菜单中找到应用的图标 + /// + public string? UOSDebIconFolder + { + set => SetValue(value); + get => GetString(); + } /// /// 应用图标文件,表示矢量图标文件。将被放入到 opt/apps/${AppId}/entries/icons/hicolor/scalable/apps/${appid}.svg 里面。矢量图标文件与 png 非矢量格式二选一,如果同时存在,优先使用矢量图标文件。 - /// 当 属性存在值时,本属性设置无效 + /// 当 属性存在值时,本属性设置无效 /// public string? SvgIconFile { @@ -363,7 +369,7 @@ public string? SvgIconFile /// /// 应用图标文件,表示 png 非矢量格式文件。将被放入到 opt/apps/${AppId}/entries/icons/hicolor/${分辨率}/apps/${appid}.png 里面。请确保实际图片分辨率正确,且是 png 格式。矢量图标文件与 png 非矢量格式二选一,如果同时存在,优先使用矢量图标文件。 - /// 当 属性存在值时,本属性设置无效 + /// 当 属性存在值时,本属性设置无效 /// public string? Png16x16IconFile { diff --git a/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs b/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs index 37d2df2..39b1950 100644 --- a/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs +++ b/DebUOS/Packaging.DebUOS/DebUOSPackageFileStructCreator.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using Packaging.DebUOS.Contexts; using Packaging.DebUOS.Contexts.Configurations; +using Walterlv.IO.PackageManagement; namespace Packaging.DebUOS; @@ -159,9 +160,22 @@ public void CreatePackagingFolder(DebUOSConfiguration configuration) } // opt\apps\AppId\entries\icons - if (File.Exists(configuration.SvgIconFile)) + var iconsFolder = Path.Join(entriesFolder, "icons"); + if (!string.IsNullOrEmpty(configuration.UOSDebIconFolder)) { - var svgFile = Path.Join(entriesFolder, "icons", "hicolor", "scalable", "apps", $"{appId}.svg"); + // 如果开发者配置了自定义的图标文件夹,则使用开发者的文件夹 + if (!Directory.Exists(configuration.UOSDebIconFolder)) + { + Logger.LogError($"配置了 Icon 文件夹的 UOSDebIconFolder 属性,但文件夹不存在 UOSDebIconFolder={configuration.UOSDebIconFolder} FullPath={Path.GetFullPath(configuration.UOSDebIconFolder)}"); + return; + } + + PackageDirectory.Copy(configuration.UOSDebIconFolder, iconsFolder); + } + else if (File.Exists(configuration.SvgIconFile)) + { + // 如果开发者配置了自定义的矢量图标文件,则优先使用矢量图标 + var svgFile = Path.Join(iconsFolder, "hicolor", "scalable", "apps", $"{appId}.svg"); Directory.CreateDirectory(Path.GetDirectoryName(svgFile)!); File.Copy(configuration.SvgIconFile, svgFile); } @@ -181,7 +195,7 @@ public void CreatePackagingFolder(DebUOSConfiguration configuration) { if (File.Exists(iconFile)) { - var pngFile = Path.Join(entriesFolder, "icons", "hicolor", resolution, "apps", $"{appId}.png"); + var pngFile = Path.Join(iconsFolder, "hicolor", resolution, "apps", $"{appId}.png"); Directory.CreateDirectory(Path.GetDirectoryName(pngFile)!); File.Copy(iconFile, pngFile); diff --git a/DebUOS/Packaging.DebUOS/Packaging.DebUOS.csproj b/DebUOS/Packaging.DebUOS/Packaging.DebUOS.csproj index f959595..d05ca82 100644 --- a/DebUOS/Packaging.DebUOS/Packaging.DebUOS.csproj +++ b/DebUOS/Packaging.DebUOS/Packaging.DebUOS.csproj @@ -21,6 +21,8 @@ + + From 4c643da925ad1d9795b937b14e8f2d61aae558e1 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 4 Jan 2024 14:49:18 +0800 Subject: [PATCH 66/74] =?UTF-8?q?=E6=9E=84=E5=BB=BA=E8=BF=87=E7=A8=8B?= =?UTF-8?q?=E4=B8=AD=E4=BC=A0=E5=85=A5=E9=85=8D=E7=BD=AE=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.NuGet/Build/package.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets index d4bc902..4939173 100644 --- a/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets +++ b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets @@ -204,8 +204,8 @@ - - + + From 898675bb2827ff19eb2f5b66012aed9f3c1e5730 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 4 Jan 2024 14:52:55 +0800 Subject: [PATCH 67/74] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8F=AA=E6=9C=89?= =?UTF-8?q?=E5=9C=A8=20windows=20=E5=B9=B3=E5=8F=B0=E6=89=8D=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E8=87=AA=E5=B7=B1=E5=86=99=E7=9A=84=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs index af65963..21e8915 100644 --- a/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using Microsoft.Extensions.Logging; using Packaging.Targets; @@ -28,9 +30,20 @@ public void PackageDeb(DirectoryInfo packingFolder, FileInfo outputDebFile, Dire { Logger.LogInformation($"开始打包。Start packaging UOS deb from '{packingFolder.FullName}' to '{outputDebFile.FullName}'"); - ArchiveBuilder archiveBuilder = new ArchiveBuilder() + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - }; + // 在 Linux 上直接使用 dpkg-deb 打包 + var process = Process.Start("dpkg-deb", new[] + { + "-b", + packingFolder.FullName, + outputDebFile.FullName, + }); + process.WaitForExit(); + return; + } + + ArchiveBuilder archiveBuilder = new ArchiveBuilder(); workingFolder ??= packingFolder; var debTarFilePath = Path.Combine(workingFolder.FullName, "deb.tar"); From b201a6e05c8807a5dd7a6e71c7019808bb5b10f7 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 4 Jan 2024 15:43:56 +0800 Subject: [PATCH 68/74] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Configurations/DebUOSConfiguration.cs | 1 - DebUOS/README.md | 51 ++++++++++++++++++- build/Version.props | 2 +- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs index 5f79f1e..bddf578 100644 --- a/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs +++ b/DebUOS/Packaging.DebUOS/Contexts/Configurations/DebUOSConfiguration.cs @@ -190,7 +190,6 @@ public string? InfoPermissions get => GetString(); } - /// /// 配置放入到 opt\apps\${AppId}\entries\applications\${AppId}.desktop 文件的 Categories 属性,可选值有:AudioVideo, Audio, Video, Development, Education, Game, Graphics, Network, Office, Science, Settings, System, Utility, Other 等。默认为 Other 的值 /// diff --git a/DebUOS/README.md b/DebUOS/README.md index 60a1299..572aafa 100644 --- a/DebUOS/README.md +++ b/DebUOS/README.md @@ -58,4 +58,53 @@ dotnet publish -t:CreateDebUOS -c release -r linux-x64 --self-contained 调试方法: - 可通过命令行参数输出了解打包参数的 `DebUOSPackingArgs.coin` 路径,从而了解打包所输入的参数。可以使用将此参数文件重新输入进行调试 -- 可通过命令行参数输出了解文件组织的 `DebUOSPacking\Packing` 文件夹路径,通过此文件夹即可了解打出的 deb 的文件夹内容,也可以切换到 Linux 服务器上使用更稳定的 `dpkg-deb` 对此文件夹进行打包 \ No newline at end of file +- 可通过命令行参数输出了解文件组织的 `DebUOSPacking\Packing` 文件夹路径,通过此文件夹即可了解打出的 deb 的文件夹内容,也可以切换到 Linux 服务器上使用更稳定的 `dpkg-deb` 对此文件夹进行打包 + +一个简单的 csproj 配置示例 + +```xml + + + + Exe + net6.0 + enable + enable + + 1.0.9 + + + + com.xxx.foo + + + + C:\lindexi\Code\foo.deb + + + + true + + + icon.png + icon32.png + + icon.svg + + + Test demo 也可以写中文 + + 测试中文名 + foo;icon + 中文测试;foo + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + +``` \ No newline at end of file diff --git a/build/Version.props b/build/Version.props index bc99561..e6e20b7 100644 --- a/build/Version.props +++ b/build/Version.props @@ -1,5 +1,5 @@ - 1.2.1-alpha22 + 1.2.1-alpha23 \ No newline at end of file From 97b5eebfd44b7c4b042c63d2e5c706c91e24e372 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 4 Jan 2024 15:53:03 +0800 Subject: [PATCH 69/74] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=92=8C=E6=84=9F=E8=B0=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.Targets/README.md | 1 + DebUOS/README.md | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 DebUOS/Packaging.Targets/README.md diff --git a/DebUOS/Packaging.Targets/README.md b/DebUOS/Packaging.Targets/README.md new file mode 100644 index 0000000..e02e122 --- /dev/null +++ b/DebUOS/Packaging.Targets/README.md @@ -0,0 +1 @@ +Copy From https://github.com/quamotion/dotnet-packaging \ No newline at end of file diff --git a/DebUOS/README.md b/DebUOS/README.md index 572aafa..fe2b2b2 100644 --- a/DebUOS/README.md +++ b/DebUOS/README.md @@ -107,4 +107,23 @@ dotnet publish -t:CreateDebUOS -c release -r linux-x64 --self-contained -``` \ No newline at end of file +``` + +## 开发 + +### 各项目作用 + +- Packaging.Targets : 来源于 [https://github.com/quamotion/dotnet-packaging](https://github.com/quamotion/dotnet-packaging) 项目,用来提供基础的打包功能 +- Packaging.DebUOS : 打出符合 UOS 规范的 deb 包的核心项目,包括组织文件夹以及使用 Packaging.Targets 创建 deb 包两个功能 +- Packaging.DebUOS.Tool : 对 Packaging.DebUOS 封装 dotnet tool 命令行工具 +- Packaging.DebUOS.NuGet : 对 Packaging.DebUOS.Tool 工具进行封装为配合 csproj 的 NuGet 包,且添加大量的和 csproj 属性对接的中间属性 + +## 参考文档 + +- [一步步教你在 Windows 上构建 dotnet 系应用的 UOS 软件安装包](https://blog.lindexi.com/post/%E4%B8%80%E6%AD%A5%E6%AD%A5%E6%95%99%E4%BD%A0%E5%9C%A8-Windows-%E4%B8%8A%E6%9E%84%E5%BB%BA-dotnet-%E7%B3%BB%E5%BA%94%E7%94%A8%E7%9A%84-UOS-%E8%BD%AF%E4%BB%B6%E5%AE%89%E8%A3%85%E5%8C%85.html ) + +- [应用打包规范 文档中心-统信UOS生态社区](https://doc.chinauos.com/content/M7kCi3QB_uwzIp6HyF5J ) + +## 感谢 + +- [https://github.com/quamotion/dotnet-packaging](https://github.com/quamotion/dotnet-packaging) From 496c50f1728fa5911a83245b7e1d6e39fb22d1e0 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 4 Jan 2024 15:53:36 +0800 Subject: [PATCH 70/74] =?UTF-8?q?=E5=87=8F=E5=B0=91=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E7=A9=BA=E9=97=B4=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.Tool/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/DebUOS/Packaging.DebUOS.Tool/Program.cs b/DebUOS/Packaging.DebUOS.Tool/Program.cs index 344c688..d194608 100644 --- a/DebUOS/Packaging.DebUOS.Tool/Program.cs +++ b/DebUOS/Packaging.DebUOS.Tool/Program.cs @@ -3,7 +3,6 @@ using System.Reflection; using System.Text; using dotnetCampus.Cli; -using dotnetCampus.Configurations; using dotnetCampus.Configurations.Core; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; From a956e7fb044369c0e6c3b3358718058067aa9b31 Mon Sep 17 00:00:00 2001 From: lindexi Date: Thu, 4 Jan 2024 16:01:23 +0800 Subject: [PATCH 71/74] =?UTF-8?q?=E5=87=86=E5=A4=87=E5=8F=91=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.NuGet/Build/package.targets | 4 ++++ DebUOS/README.md | 8 ++++++++ build/Version.props | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets index 4939173..4e76642 100644 --- a/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets +++ b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets @@ -1,5 +1,9 @@ + + + + diff --git a/DebUOS/README.md b/DebUOS/README.md index fe2b2b2..bbbb66b 100644 --- a/DebUOS/README.md +++ b/DebUOS/README.md @@ -51,6 +51,14 @@ dotnet publish -t:CreateDebUOS -c release -r linux-x64 --self-contained 以上命令行与传统的发布命令最大的不同在于添加了 `-t:CreateDebUOS` 参数,通过此参数即可触发名为 `CreateDebUOS` 的 Target 进行创建 deb 包 +如期望自动在发布之后输出符合 UOS 规范的 deb 包,期望不添加 `-t:CreateDebUOS` 参数,则可以通过配置 `true` 属性到 csproj 从而实现在发布之后,自动执行打包,如以下代码 + +```xml + + true + +``` + 通过 NuGet 包配置的方法,可以很方便进行接入,自带大量的默认配置,从零开始接入的成本低,且不需要有许多额外的知识。可以完全复用原有的构建工具链,可以配合其他工具实现一次打包创建多个平台的安装包,可以将各项配置写入到 csproj 里面方便客制化定制以及接入更多自动化参数和加入代码管理 更多可配置属性请参阅 DebUOSConfiguration.cs 文件 diff --git a/build/Version.props b/build/Version.props index e6e20b7..e1307c6 100644 --- a/build/Version.props +++ b/build/Version.props @@ -1,5 +1,5 @@ - 1.2.1-alpha23 + 1.2.1-alpha25 \ No newline at end of file From 8e60527823cbeb5b6c9a0c0880f91f85ed9f289e Mon Sep 17 00:00:00 2001 From: lindexi Date: Fri, 5 Jan 2024 09:35:07 +0800 Subject: [PATCH 72/74] =?UTF-8?q?=E5=A4=B4=E9=93=81=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=EF=BC=8C=E5=8D=B3=E4=BD=BF=20Linux=20=E4=B8=8A=E4=B9=9F?= =?UTF-8?q?=E7=94=A8=E5=92=B1=E8=87=AA=E5=B7=B1=E7=9A=84=E6=89=93=E5=8C=85?= =?UTF-8?q?=E7=AE=97=E6=B3=95=20=E8=A6=81=E6=98=AF=E8=B7=91=E4=B8=8D?= =?UTF-8?q?=E4=BA=86=EF=BC=8C=E5=B0=B1=E8=AF=B7=E7=94=A8=E6=88=B7=E8=87=AA?= =?UTF-8?q?=E5=B7=B1=E6=89=8B=E5=8A=A8=E6=89=A7=E8=A1=8C=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=20=E5=B0=9D=E8=AF=95=E4=BF=AE=E5=A4=8D=20CentOS=20/?= =?UTF-8?q?=20ArchLinux=20=E7=AD=89=20=E9=9D=9E=20Debian=20=E7=B3=BB?= =?UTF-8?q?=E7=9A=84=E7=B3=BB=E7=BB=9F=EF=BC=8C=E9=BB=98=E8=AE=A4=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=20dpkg-deb=20=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs b/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs index 21e8915..b97bbbc 100644 --- a/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs +++ b/DebUOS/Packaging.DebUOS/DebUOSPackageCreator.cs @@ -30,19 +30,6 @@ public void PackageDeb(DirectoryInfo packingFolder, FileInfo outputDebFile, Dire { Logger.LogInformation($"开始打包。Start packaging UOS deb from '{packingFolder.FullName}' to '{outputDebFile.FullName}'"); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - // 在 Linux 上直接使用 dpkg-deb 打包 - var process = Process.Start("dpkg-deb", new[] - { - "-b", - packingFolder.FullName, - outputDebFile.FullName, - }); - process.WaitForExit(); - return; - } - ArchiveBuilder archiveBuilder = new ArchiveBuilder(); workingFolder ??= packingFolder; From e351de121aff97a07484c6b6fe8484f08a93aa17 Mon Sep 17 00:00:00 2001 From: lindexi Date: Fri, 5 Jan 2024 09:35:49 +0800 Subject: [PATCH 73/74] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E5=86=99=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.NuGet/Build/package.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets index 4e76642..cb847a1 100644 --- a/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets +++ b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets @@ -243,12 +243,12 @@ - + - + From d7a8c41149987949dbf7fccf856b351744ea9996 Mon Sep 17 00:00:00 2001 From: lindexi Date: Fri, 5 Jan 2024 09:37:17 +0800 Subject: [PATCH 74/74] =?UTF-8?q?=E6=9B=B4=E6=AD=A3=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebUOS/Packaging.DebUOS.NuGet/Build/package.targets | 1 + 1 file changed, 1 insertion(+) diff --git a/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets index cb847a1..094356f 100644 --- a/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets +++ b/DebUOS/Packaging.DebUOS.NuGet/Build/package.targets @@ -6,6 +6,7 @@ + $([MSBuild]::NormalizePath($(IntermediateOutputPath), 'DebUOSPacking'))