Browse Source

Created DotApp builder

Krzysztof Krysiński 6 months ago
parent
commit
17e6c1e081

+ 2 - 1
.gitignore

@@ -338,4 +338,5 @@ Installer/Assets
 
 GitIgnore
 
-Cache/
+Cache/
+.DS_Store

+ 1 - 1
src/PixiEditor.AnimationRenderer.FFmpeg/PixiEditor.AnimationRenderer.FFmpeg.csproj

@@ -17,7 +17,7 @@
       <PackageReference Include="FFMpegCore" Version="5.1.0" />
     </ItemGroup>
   
-    <ItemGroup>
+    <ItemGroup Condition="$(RuntimeIdentifier.StartsWith('win-'))">
       <Content Include="ThirdParty\Windows\ffmpeg\**">
         <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       </Content>

+ 48 - 0
src/PixiEditor.Builder/build/CreateAppPackageTask.cs

@@ -0,0 +1,48 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using Cake.Common;
+using Cake.Core;
+using Cake.Core.Diagnostics;
+using Cake.Frosting;
+using PixiEditor.Cake.Builder.PackageBuilders;
+
+namespace PixiEditor.Cake.Builder;
+
+[IsDependentOn(typeof(CopyExtensionsTask))]
+public sealed class CreateAppPackageTask : FrostingTask<BuildContext>
+{
+    public override bool ShouldRun(BuildContext context)
+    {
+        return context.BuildPackage;
+    }
+
+    public override void Run(BuildContext context)
+    {
+        var builders = typeof(CreateAppPackageTask).Assembly.GetTypes()
+            .Select(x => (x, x.GetCustomAttribute<PackageInfoAttribute>())).Where(x => x.Item2 != null);
+
+        foreach (var builder in builders)
+        {
+            if (string.Equals(builder.Item2.PackageName, context.PackageType, StringComparison.InvariantCultureIgnoreCase))
+            {
+                var packageBuilder = (PackageBuilder)Activator.CreateInstance(builder.x);
+                if (packageBuilder == null)
+                {
+                    throw new InvalidOperationException($"Could not create instance of {builder.x.Name}");
+                }
+
+                context.Log.Information($"Building package {builder.Item2.PackageName}");
+                var info = packageBuilder.BuildPackage(context);
+                if (info.Success)
+                {
+                    context.Log.Information($"Package built successfully to {info.PathToPackage}");
+                }
+                else
+                {
+                    context.Log.Error($"Failed to build package {builder.Item2.PackageName} with error: {info.Error}");
+                }
+            }
+        }
+    }
+}

+ 56 - 0
src/PixiEditor.Builder/build/PackageBuilders/DotAppPackageBuilder.cs

@@ -0,0 +1,56 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Xml.Linq;
+using Cake.Core.Diagnostics;
+
+namespace PixiEditor.Cake.Builder.PackageBuilders;
+
+[PackageInfo("DotApp")]
+public sealed class DotAppPackageBuilder : PackageBuilder
+{
+    public override PackageBuildInfo BuildPackage(BuildContext context)
+    {
+        string plistPath = Path.Combine(context.PackageProjectPath, "Info.plist");
+
+        if (!File.Exists(plistPath))
+        {
+            return PackageBuildInfo.Fail($"Info.plist not found at {plistPath}");
+        }
+
+        string packagePath = Path.Combine(context.OutputDirectory, "package");
+        
+        string path = Path.Combine(packagePath, "PixiEditor.app");
+        CreateDirectoryIfMissing(path);
+
+        string contentsPath = Path.Combine(path, "Contents");
+        CreateDirectoryIfMissing(contentsPath);
+
+        string resourcesPath = Path.Combine(contentsPath, "Resources");
+        CreateDirectoryIfMissing(resourcesPath);
+
+        string macOsPath = Path.Combine(contentsPath, "MacOS");
+        CreateDirectoryIfMissing(macOsPath);
+
+        string codeSignaturesPath = Path.Combine(contentsPath, "_CodeSignature");
+        CreateDirectoryIfMissing(codeSignaturesPath);
+
+        string dllContentsPath = context.OutputDirectory;
+        CopyFilesOverwrite(dllContentsPath, macOsPath, true, packagePath);
+        
+        string pilst = File.ReadAllText(plistPath);
+        
+        var assembly = Assembly.LoadFile(Path.Combine(dllContentsPath, "PixiEditor.dll"));
+        
+        pilst = pilst.Replace("{version-string}", assembly.GetName().Version.ToString());
+
+        string targetPilstPath = Path.Combine(contentsPath, "Info.plist");
+        
+        File.WriteAllText(targetPilstPath, pilst);
+
+        File.Copy(Path.Combine(context.PackageProjectPath, "PixiEditor.icns"),
+            Path.Combine(resourcesPath, "PixiEditor.icns"), true);
+
+        return PackageBuildInfo.Succeed(path);
+    }
+}

+ 72 - 0
src/PixiEditor.Builder/build/PackageBuilders/PackageBuilder.cs

@@ -0,0 +1,72 @@
+using System.IO;
+
+namespace PixiEditor.Cake.Builder.PackageBuilders;
+
+public abstract class PackageBuilder
+{
+    public abstract PackageBuildInfo BuildPackage(BuildContext context);
+
+    protected void CreateDirectoryIfMissing(string path, bool clean = true)
+    {
+        if (clean && Directory.Exists(path))
+        {
+            Directory.Delete(path, true);
+        }
+
+        if (!Directory.Exists(path))
+        {
+            Directory.CreateDirectory(path);
+        }
+    }
+
+    protected void CopyFilesOverwrite(string sourcePath, string destinationPath, bool recursive = true,
+        string ignore = "")
+    {
+        if (sourcePath == ignore)
+        {
+            return;
+        }
+
+        CreateDirectoryIfMissing(destinationPath);
+        foreach (string file in Directory.GetFiles(sourcePath))
+        {
+            string fileName = Path.GetFileName(file);
+            string destFile = Path.Combine(destinationPath, fileName);
+            File.Copy(file, destFile, true);
+        }
+
+        if (recursive)
+        {
+            foreach (string dir in Directory.GetDirectories(sourcePath))
+            {
+                string dirName = Path.GetFileName(dir);
+                string destDir = Path.Combine(destinationPath, dirName);
+                CopyFilesOverwrite(dir, destDir, true, ignore);
+            }
+        }
+    }
+}
+
+public struct PackageBuildInfo
+{
+    public bool Success { get; set; }
+    public string PathToPackage { get; set; }
+
+    public string? Error { get; set; }
+
+    public PackageBuildInfo(bool success, string pathToPackage)
+    {
+        Success = success;
+        PathToPackage = pathToPackage;
+    }
+
+    public static PackageBuildInfo Succeed(string pathToPackage)
+    {
+        return new PackageBuildInfo(true, pathToPackage);
+    }
+
+    public static PackageBuildInfo Fail(string error)
+    {
+        return new PackageBuildInfo(false, null);
+    }
+}

+ 14 - 0
src/PixiEditor.Builder/build/PackageBuilders/PackageInfoAttribute.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace PixiEditor.Cake.Builder.PackageBuilders;
+
+[AttributeUsage(AttributeTargets.Class)]
+public class PackageInfoAttribute : Attribute
+{
+    public string PackageName { get; }
+
+    public PackageInfoAttribute(string packageName)
+    {
+        PackageName = packageName;
+    }
+}

+ 36 - 6
src/PixiEditor.Builder/build/Program.cs

@@ -1,6 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.IO;
 using Cake.Common.Build;
 using Cake.Common.Tools.DotNet;
+using Cake.Common.Tools.DotNet.MSBuild;
 using Cake.Common.Tools.DotNet.Publish;
 using Cake.Core;
 using Cake.Core.Diagnostics;
@@ -37,8 +41,16 @@ public class BuildContext : FrostingContext
 
     public bool SelfContained { get; set; } = false;
 
+    public bool UseAppHost { get; set; } = false;
+
     public string Runtime { get; set; }
 
+    public bool BuildPackage { get; set; } = false;
+
+    public string PackageType { get; set; }
+
+    public string PackageProjectPath { get; set; }
+
     public BuildContext(ICakeContext context)
         : base(context)
     {
@@ -69,13 +81,27 @@ public class BuildContext : FrostingContext
             OutputDirectory = context.Arguments.GetArgument("o");
         }
 
-        bool hasSelfContained = context.Arguments.HasArgument("self-contained");
-        if (hasSelfContained)
-        {
-            SelfContained = true;
-        }
+        SelfContained = context.Arguments.HasArgument("self-contained");
 
         Runtime = context.Arguments.GetArgument("runtime");
+
+        BuildPackage = context.Arguments.HasArgument("build-package");
+
+        PackageProjectPath = context.Arguments.GetArgument("package-proj-path");
+
+        if (BuildPackage)
+        {
+            PackageType = context.Arguments.GetArgument("build-package");
+            if (string.Equals(PackageType, "DotApp", StringComparison.CurrentCultureIgnoreCase))
+            {
+                UseAppHost = true;
+            }
+
+            if (string.IsNullOrEmpty(PackageProjectPath))
+            {
+                PackageProjectPath = PathToProject;
+            }
+        }
     }
 
     private static string GetArgumentOrDefault(ICakeContext context, string argumentName, string defaultValue)
@@ -88,7 +114,7 @@ public class BuildContext : FrostingContext
 }
 
 [TaskName("Default")]
-[IsDependentOn(typeof(CopyExtensionsTask))]
+[IsDependentOn(typeof(CreateAppPackageTask))]
 public sealed class DefaultTask : FrostingTask<BuildContext>
 {
     public override void Run(BuildContext context)
@@ -138,6 +164,10 @@ public sealed class BuildProjectTask : FrostingTask<BuildContext>
             Configuration = context.BuildConfiguration,
             SelfContained = context.SelfContained,
             Runtime = context.Runtime,
+            MSBuildSettings = new DotNetMSBuildSettings()
+            {
+                Properties = { ["UseAppHost"] = [context.UseAppHost.ToString()] },
+            },
             OutputDirectory = context.OutputDirectory,
         };
 

+ 43 - 0
src/PixiEditor.Desktop/Info.plist

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+    <dict>
+        <key>CFBundleName</key>
+        <string>PixiEditor</string>
+        <key>CFBundleDisplayName</key>
+        <string>PixiEditor.Desktop</string>
+        <key>CFBundleIdentifier</key>
+        <string>com.pixieditor.macos</string>
+        <key>CFBundleVersion</key>
+        <string>{version-string}</string>
+        <key>CFBundlePackageType</key>
+        <string>APPL</string>
+        <key>CFBundleExecutable</key>
+        <string>PixiEditor</string>
+        <key>CFBundleIconFile</key>
+        <string>PixiEditor.icns</string>
+        <key>CFBundleShortVersionString</key>
+        <string>{version-string}</string>
+        <key>NSPrincipalClass</key>
+        <string>NSApplication</string>
+        <key>NSHighResolutionCapable</key>
+        <key>CFBundleDocumentTypes</key>
+        <array>
+            <dict>
+                <key>CFBundleTypeName</key>
+                <string>Pixi Document</string>
+                <key>CFBundleTypeExtensions</key>
+                <array>
+                    <string>pixi</string>
+                </array>
+                <key>CFBundleTypeIconFile</key>
+                <string>PixiEditor.icns</string>
+                <key>CFBundleTypeRole</key>
+                <string>Editor</string>
+                <key>LSHandlerRank</key>
+                <string>Owner</string>
+            </dict>
+        </array>
+        <true/>
+    </dict>
+</plist>

+ 12 - 9
src/PixiEditor.Desktop/PixiEditor.Desktop.csproj

@@ -10,7 +10,6 @@
     <RootNamespace>PixiEditor.Desktop</RootNamespace>
     <ApplicationIcon>..\PixiEditor\Images\favicon.ico</ApplicationIcon>
   </PropertyGroup>
-  
 
   <PropertyGroup>
     <ApplicationManifest>app.manifest</ApplicationManifest>
@@ -27,7 +26,11 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0"/>
+  </ItemGroup>
+
+  <ItemGroup Condition="$([MSBuild]::IsOsPlatform('OSX'))">
+    <PackageReference Include="Dotnet.Bundle" Version="0.9.13"/>
   </ItemGroup>
 
   <ItemGroup>
@@ -37,16 +40,16 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\PixiEditor\PixiEditor.csproj" />
+    <ProjectReference Include="..\PixiEditor\PixiEditor.csproj"/>
   </ItemGroup>
 
-  <Target Name="Rename" AfterTargets="AfterBuild">
-    <Move SourceFiles="$(OutDir)PixiEditor.Desktop.exe" DestinationFiles="$(OutDir)PixiEditor.exe" />
-    <Message Text="Renamed build executable file." Importance="high" />
+  <Target Name="Rename" AfterTargets="AfterBuild" Condition="$([MSBuild]::IsOsPlatform('Windows'))">
+    <Move SourceFiles="$(OutDir)PixiEditor.Desktop.exe" DestinationFiles="$(OutDir)PixiEditor.exe"/>
+    <Message Text="Renamed build executable file." Importance="high"/>
   </Target>
 
-  <Target Name="Rename" AfterTargets="Publish">
-    <Move SourceFiles="$(PublishDir)PixiEditor.Desktop.exe" DestinationFiles="$(PublishDir)PixiEditor.exe" />
-    <Message Text="Renamed published executable file." Importance="high" />
+  <Target Name="Rename" AfterTargets="Publish" Condition="$([MSBuild]::IsOsPlatform('Windows'))">
+    <Move SourceFiles="$(PublishDir)PixiEditor.Desktop.exe" DestinationFiles="$(PublishDir)PixiEditor.exe"/>
+    <Message Text="Renamed published executable file." Importance="high"/>
   </Target>
 </Project>

BIN
src/PixiEditor.Desktop/PixiEditor.icns


+ 5 - 3
src/PixiEditor.MacOs/todo.md

@@ -1,9 +1,11 @@
 - [x] Input keys
-- [ ] Default shortcuts
+- [x] Default shortcuts
+- [ ] Package - builder
+- [ ] FFmpeg
 - [ ] Single instance
-- [ ] File associations
+- [ ] File associations (pixi, other formats, lospec protocol)
 - [ ] Autoupdates
-- [ ] Process handling
+- [x] Process handling
 - [ ] Check if extensions work
 - [ ] Native menu
 - [ ] Performance fixes