WinGetLocalSource.cs (10633B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 4 namespace Microsoft.WinGetSourceCreator 5 { 6 using global::WinGetSourceCreator.Model; 7 using System.Text.Json.Serialization; 8 using System.Text.Json; 9 using Microsoft.WinGetUtil.Api; 10 using Microsoft.WinGetUtil.Interfaces; 11 12 public class WinGetLocalSource 13 { 14 private readonly string workingDirectory; 15 private readonly ManifestTokens tokens; 16 private readonly Signature? signature; 17 18 public static void CreateFromLocalSourceFile(string localSourceFile) 19 { 20 var content = File.ReadAllText(localSourceFile); 21 content = Environment.ExpandEnvironmentVariables(content); 22 23 var options = new JsonSerializerOptions 24 { 25 PropertyNameCaseInsensitive = true, 26 Converters = 27 { 28 new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) 29 } 30 }; 31 32 content = content.Replace("\\", "/"); 33 34 var localSource = JsonSerializer.Deserialize<LocalSource>(content, options); 35 if (localSource == null) 36 { 37 throw new Exception("Failed deserializing"); 38 } 39 40 CreateLocalSource(localSource); 41 } 42 43 public static void CreateLocalSource(LocalSource localSource) 44 { 45 localSource.Validate(); 46 47 var wingetSource = new WinGetLocalSource(localSource.WorkingDirectory, localSource.Signature); 48 49 if (localSource.LocalInstallers != null) 50 { 51 foreach (var installer in localSource.LocalInstallers) 52 { 53 wingetSource.PrepareLocalInstaller(installer); 54 } 55 } 56 57 if (localSource.DynamicInstallers != null) 58 { 59 foreach (var installer in localSource.DynamicInstallers) 60 { 61 wingetSource.PrepareDynamicInstaller(installer); 62 } 63 } 64 65 foreach (var localManifest in localSource.LocalManifests) 66 { 67 wingetSource.PrepareManifest(localManifest); 68 } 69 70 var indexV2File = wingetSource.CreateIndex(localSource.GetIndexName(), 2, 0); 71 _ = wingetSource.CreatePackage(localSource.GetSourceName(2), localSource.AppxManifest, indexV2File, localSource.Signature); 72 73 var indexV1File = wingetSource.CreateIndex(localSource.GetIndexName()); 74 _ = wingetSource.CreatePackage(localSource.GetSourceName(1), localSource.AppxManifest, indexV1File, localSource.Signature); 75 } 76 77 public WinGetLocalSource(string workingDirectory, Signature? signature) 78 { 79 this.workingDirectory = Path.GetFullPath(workingDirectory); 80 81 if (Directory.Exists(workingDirectory)) 82 { 83 Directory.Delete(workingDirectory, true); 84 } 85 Directory.CreateDirectory(workingDirectory); 86 87 this.tokens = new(); 88 this.signature = signature; 89 } 90 91 public void PrepareDynamicInstaller(DynamicInstaller installer) 92 { 93 var sourceInstaller = new SourceInstaller(this.workingDirectory, installer); 94 PrepareInstaller(sourceInstaller); 95 } 96 97 public void PrepareLocalInstaller(LocalInstaller installer) 98 { 99 var sourceInstaller = new SourceInstaller(this.workingDirectory, installer); 100 PrepareInstaller(sourceInstaller); 101 } 102 103 public void PrepareManifest(string input) 104 { 105 if (File.Exists(input)) 106 { 107 108 CopyManifestFile(input, Path.Combine(this.workingDirectory, Path.GetFileName(input))); 109 } 110 else 111 { 112 CopyManifestFiles(input, this.workingDirectory); 113 } 114 } 115 116 public string CreateIndex(string indexName, uint? majorVersion = null, uint? minorVersion = null) 117 { 118 string fullPath = Path.Combine(this.workingDirectory, indexName); 119 120 if (File.Exists(fullPath)) 121 { 122 File.Delete(fullPath); 123 } 124 125 WinGetFactory factory = new (); 126 using IWinGetSQLiteIndex indexHelper = majorVersion == null ? factory.SQLiteIndexCreateLatestVersion(fullPath) : factory.SQLiteIndexCreate(fullPath, majorVersion.Value, minorVersion.GetValueOrDefault()); 127 128 Queue<string> filesQueue = new(Directory.EnumerateFiles(this.workingDirectory, "*.yaml", SearchOption.AllDirectories)); 129 while (filesQueue.Count > 0) 130 { 131 int currentCount = filesQueue.Count; 132 133 for (int i = 0; i < currentCount; i++) 134 { 135 string file = filesQueue.Dequeue(); 136 try 137 { 138 var rel = Path.GetRelativePath(this.workingDirectory, file); 139 indexHelper.AddManifest(file, rel); 140 } 141 catch 142 { 143 // If adding manifest to index fails, add to queue and try again. 144 // This can occur if there is a package dependency that has not yet been added to the index. 145 filesQueue.Enqueue(file); 146 } 147 } 148 149 if (filesQueue.Count == currentCount) 150 { 151 throw new InvalidOperationException("Failed to add all manifests in directory to index."); 152 } 153 } 154 155 indexHelper.PrepareForPackaging(); 156 157 return fullPath; 158 } 159 160 public string CreatePackage(string packageName, string inputAppxManifestFile, string indexPath, Signature? signature) 161 { 162 if (!File.Exists(inputAppxManifestFile)) 163 { 164 throw new FileNotFoundException(inputAppxManifestFile); 165 } 166 167 if (!File.Exists(indexPath)) 168 { 169 throw new FileNotFoundException(indexPath); 170 } 171 172 string appxManifestFile = Path.Combine(this.workingDirectory, "AppxManifest.xml"); 173 File.Copy(inputAppxManifestFile, appxManifestFile, true); 174 175 if (signature != null && signature.Publisher != null) 176 { 177 Helpers.ModifyAppxManifestIdentity(appxManifestFile, signature.Publisher); 178 } 179 180 string mappingFile = Path.Combine(this.workingDirectory, "MappingFile.txt"); 181 182 { 183 using StreamWriter outputFile = new(mappingFile, false); 184 outputFile.WriteLine("[Files]"); 185 outputFile.WriteLine($"\"{indexPath}\" \"Public\\{Path.GetFileName(indexPath)}\""); 186 outputFile.WriteLine($"\"{appxManifestFile}\" \"AppxManifest.xml\""); 187 } 188 189 string outputPackage = Path.Combine(this.workingDirectory, packageName); 190 Helpers.PackWithMappingFile(outputPackage, mappingFile); 191 192 if (signature != null) 193 { 194 Helpers.SignFile(outputPackage, signature); 195 } 196 197 return outputPackage; 198 } 199 200 // Copies all .yaml files 201 private void CopyManifestFiles(string sourceDir, string destDir) 202 { 203 DirectoryInfo dir = new DirectoryInfo(sourceDir); 204 DirectoryInfo[] dirs = dir.GetDirectories(); 205 206 FileInfo[] files = dir.GetFiles(); 207 foreach (FileInfo file in files) 208 { 209 if (file.Extension == ".yaml") 210 { 211 CopyManifestFile(file.FullName, Path.Combine(destDir, file.Name)); 212 } 213 } 214 215 foreach (DirectoryInfo subdir in dirs) 216 { 217 CopyManifestFiles(subdir.FullName, Path.Combine(destDir, subdir.Name)); 218 } 219 } 220 221 // Copies a file and replaces any token found. 222 private void CopyManifestFile(string sourceFile, string destinationFile) 223 { 224 if (!File.Exists(sourceFile)) 225 { 226 throw new FileNotFoundException(sourceFile); 227 } 228 229 var content = File.ReadAllText(sourceFile); 230 231 foreach (var token in this.tokens.Tokens) 232 { 233 if (content.Contains(token.Key)) 234 { 235 content = content.Replace(token.Key, token.Value); 236 } 237 } 238 239 File.WriteAllText(destinationFile, content); 240 } 241 242 private void PrepareInstaller(SourceInstaller installer) 243 { 244 // Sign installer if needed. 245 if (!installer.SkipSignature) 246 { 247 var sig = this.GetSignature(installer); 248 if (sig != null) 249 { 250 Helpers.SignInstaller(installer, sig); 251 } 252 } 253 254 // Process hash token if needed. 255 if (!string.IsNullOrEmpty(installer.HashToken)) 256 { 257 this.tokens.AddHashToken(installer.InstallerFile, installer.HashToken); 258 } 259 260 // Extra steps. 261 // An msix can include the signature token. 262 if (installer.Type == InstallerType.Msix) 263 { 264 if (!string.IsNullOrEmpty(installer.SignatureToken)) 265 { 266 var signatureFilePath = Helpers.GetSignatureFileFromMsix(installer.InstallerFile); 267 this.tokens.AddHashToken(signatureFilePath, installer.SignatureToken); 268 269 try 270 { 271 var dir = Path.GetDirectoryName(signatureFilePath); 272 if (!string.IsNullOrEmpty(dir)) 273 { 274 Directory.Delete(dir, true); 275 } 276 } 277 catch (Exception) 278 { 279 } 280 } 281 } 282 } 283 284 private Signature? GetSignature(Installer installer) 285 { 286 if (installer.Type == InstallerType.Zip) 287 { 288 return null; 289 } 290 291 return installer.Signature == null ? this.signature : installer.Signature; 292 } 293 } 294 }