diff --git a/.editorconfig b/.editorconfig index fd67d38..429d8e6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,7 +12,10 @@ indent_style = space indent_size = 4 trim_trailing_whitespace = true -[project.json] +[{project.json,appsettings.json,appsettings.*.json}] +indent_size = 2 + +[*.{yaml,yml}] indent_size = 2 # C# and Visual Basic files diff --git a/.gitattributes b/.gitattributes index 1691f22..cdc29e3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,5 @@ *.png filter=lfs diff=lfs merge=lfs -text *.jpg filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.avif filter=lfs diff=lfs merge=lfs -text +*.webp filter=lfs diff=lfs merge=lfs -text diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index a728f9c..462b09b 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -1,29 +1,30 @@ name: Build blog docker image on: - push: - branches: - - master + push: + branches: + - master jobs: - Build-Blog-Image: - runs-on: archlinux - steps: - - uses: https://git.rrricardo.top/actions/checkout@v4 - name: Check out code - with: - lfs: true - - name: Build project - run: | - cd YaeBlog - dotnet publish - - name: Build docker image - run: | - cd YaeBlog - docker build . -t registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest - - name: Login aliyun docker registry - uses: https://git.rrricardo.top/actions/login-action@v3 - with: - registry: registry.cn-beijing.aliyuncs.com - username: 初冬的朝阳 - password: ${{ secrets.ALIYUN_PASSWORD }} - - name: Push docker image - run: docker push registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest + Build-Blog-Image: + runs-on: archlinux + steps: + - name: Check out code. + uses: http://github-mirrors.infra.svc.cluster.local/actions/checkout.git@v4 + with: + lfs: true + - name: Build project. + run: | + git submodule update --init + podman pull mcr.azure.cn/dotnet/aspnet:10.0 + pwsh build.ps1 build + - name: Workaround to make sure podman-login working. + run: | + mkdir -p /root/.docker + - name: Login tencent cloud docker registry. + uses: http://github-mirrors.infra.svc.cluster.local/actions/podman-login.git@v1 + with: + registry: ccr.ccs.tencentyun.com + username: 100044380877 + password: ${{ secrets.TENCENT_REGISTRY_PASSWORD }} + auth_file_path: /etc/containers/auth.json + - name: Push docker image. + run: podman push ccr.ccs.tencentyun.com/jackfiled/blog:latest diff --git a/.gitignore b/.gitignore index f606da8..c7e73eb 100644 --- a/.gitignore +++ b/.gitignore @@ -184,6 +184,7 @@ DocProject/Help/html # Click-Once directory publish/ +out/ # Publish Web Output *.[Pp]ublish.xml @@ -484,4 +485,4 @@ $RECYCLE.BIN/ *.swp # Tailwind auto-generated stylesheet -output.css +*.g.css diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..27565ed --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "third-party/BlazorSvgComponents"] + path = third-party/BlazorSvgComponents + url = https://git.rrricardo.top/jackfiled/BlazorSvgComponents.git diff --git a/YaeBlog.sln b/YaeBlog.sln deleted file mode 100644 index c4f8f9c..0000000 --- a/YaeBlog.sln +++ /dev/null @@ -1,41 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YaeBlog", "YaeBlog\YaeBlog.csproj", "{20438EFD-8DDE-43AF-92E2-76495C29233C}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".gitea", ".gitea", "{9B5AAA29-37D8-454A-8D8F-3E6B6BCF38E6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ADBC3DA8-F65C-4B5D-A97A-DC351F8E6592}" - ProjectSection(SolutionItems) = preProject - .gitea\workflows\build.yaml = .gitea\workflows\build.yaml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{753B998C-1B9E-498F-B949-845CE86C4075}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitattributes = .gitattributes - .gitignore = .gitignore - README.md = README.md - LICENSE = LICENSE - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {20438EFD-8DDE-43AF-92E2-76495C29233C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {20438EFD-8DDE-43AF-92E2-76495C29233C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {20438EFD-8DDE-43AF-92E2-76495C29233C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {20438EFD-8DDE-43AF-92E2-76495C29233C}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {ADBC3DA8-F65C-4B5D-A97A-DC351F8E6592} = {9B5AAA29-37D8-454A-8D8F-3E6B6BCF38E6} - EndGlobalSection -EndGlobal diff --git a/YaeBlog.slnx b/YaeBlog.slnx new file mode 100644 index 0000000..8b6679e --- /dev/null +++ b/YaeBlog.slnx @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/YaeBlog/Abstraction/IEssayScanService.cs b/YaeBlog/Abstraction/IEssayScanService.cs deleted file mode 100644 index f5aad84..0000000 --- a/YaeBlog/Abstraction/IEssayScanService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using YaeBlog.Models; - -namespace YaeBlog.Abstraction; - -public interface IEssayScanService -{ - public Task ScanContents(); - - public Task SaveBlogContent(BlogContent content, bool isDraft = true); - - public Task ScanImages(); -} diff --git a/YaeBlog/Commands/Binders/BlogOptionsBinder.cs b/YaeBlog/Commands/Binders/BlogOptionsBinder.cs deleted file mode 100644 index f6e4af6..0000000 --- a/YaeBlog/Commands/Binders/BlogOptionsBinder.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.CommandLine.Binding; -using System.Text.Json; -using Microsoft.Extensions.Options; -using YaeBlog.Models; - -namespace YaeBlog.Commands.Binders; - -public sealed class BlogOptionsBinder : BinderBase> -{ - protected override IOptions GetBoundValue(BindingContext bindingContext) - { - bindingContext.AddService>(_ => - { - FileInfo settings = new(Path.Combine(Environment.CurrentDirectory, "appsettings.json")); - if (!settings.Exists) - { - throw new InvalidOperationException("Failed to load YaeBlog configurations."); - } - - using StreamReader reader = settings.OpenText(); - using JsonDocument document = JsonDocument.Parse(reader.ReadToEnd()); - JsonElement root = document.RootElement; - JsonElement optionSection = root.GetProperty(BlogOptions.OptionName); - - BlogOptions? result = optionSection.Deserialize(); - if (result is null) - { - throw new InvalidOperationException("Failed to load YaeBlog configuration in appsettings.json."); - } - - return new OptionsWrapper(result); - }); - - return bindingContext.GetRequiredService>(); - } -} diff --git a/YaeBlog/Commands/Binders/EssayScanServiceBinder.cs b/YaeBlog/Commands/Binders/EssayScanServiceBinder.cs deleted file mode 100644 index 5d7e1d5..0000000 --- a/YaeBlog/Commands/Binders/EssayScanServiceBinder.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.CommandLine.Binding; -using Microsoft.Extensions.Options; -using YaeBlog.Abstraction; -using YaeBlog.Models; -using YaeBlog.Services; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -namespace YaeBlog.Commands.Binders; - -public sealed class EssayScanServiceBinder : BinderBase -{ - protected override IEssayScanService GetBoundValue(BindingContext bindingContext) - { - bindingContext.AddService(provider => - { - DeserializerBuilder deserializerBuilder = new(); - deserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance); - deserializerBuilder.IgnoreUnmatchedProperties(); - - SerializerBuilder serializerBuilder = new(); - serializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance); - - IOptions options = provider.GetRequiredService>(); - ILogger logger = provider.GetRequiredService>(); - - return new EssayScanService(serializerBuilder.Build(), deserializerBuilder.Build(), options, logger); - }); - - return bindingContext.GetRequiredService(); - } -} diff --git a/YaeBlog/Commands/Binders/LoggerBinder.cs b/YaeBlog/Commands/Binders/LoggerBinder.cs deleted file mode 100644 index 47bd24c..0000000 --- a/YaeBlog/Commands/Binders/LoggerBinder.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.CommandLine.Binding; - -namespace YaeBlog.Commands.Binders; - -public sealed class LoggerBinder : BinderBase> -{ - protected override ILogger GetBoundValue(BindingContext bindingContext) - { - bindingContext.AddService(_ => LoggerFactory.Create(builder => builder.AddConsole())); - bindingContext.AddService>(provider => - { - ILoggerFactory factory = provider.GetRequiredService(); - return factory.CreateLogger(); - }); - - return bindingContext.GetRequiredService>(); - } -} diff --git a/YaeBlog/Commands/YaeBlogCommand.cs b/YaeBlog/Commands/YaeBlogCommand.cs deleted file mode 100644 index 94815c5..0000000 --- a/YaeBlog/Commands/YaeBlogCommand.cs +++ /dev/null @@ -1,231 +0,0 @@ -using System.CommandLine; -using YaeBlog.Commands.Binders; -using YaeBlog.Components; -using YaeBlog.Extensions; -using YaeBlog.Models; -using YaeBlog.Services; - -namespace YaeBlog.Commands; - -public sealed class YaeBlogCommand -{ - private readonly RootCommand _rootCommand = new("YaeBlog Cli"); - - public YaeBlogCommand() - { - AddServeCommand(_rootCommand); - AddWatchCommand(_rootCommand); - AddListCommand(_rootCommand); - AddNewCommand(_rootCommand); - AddPublishCommand(_rootCommand); - AddScanCommand(_rootCommand); - } - - public Task RunAsync(string[] args) - { - return _rootCommand.InvokeAsync(args); - } - - private static void AddServeCommand(RootCommand rootCommand) - { - Command serveCommand = new("serve", "Start http server."); - rootCommand.AddCommand(serveCommand); - - serveCommand.SetHandler(async context => - { - WebApplicationBuilder builder = WebApplication.CreateBuilder(); - - builder.Services.AddRazorComponents() - .AddInteractiveServerComponents(); - builder.Services.AddControllers(); - builder.AddYaeBlog(); - builder.AddServer(); - - WebApplication application = builder.Build(); - - application.UseStaticFiles(); - application.UseAntiforgery(); - application.UseYaeBlog(); - - application.MapRazorComponents() - .AddInteractiveServerRenderMode(); - application.MapControllers(); - - CancellationToken token = context.GetCancellationToken(); - await application.RunAsync(token); - }); - } - - private static void AddWatchCommand(RootCommand rootCommand) - { - Command command = new("watch", "Start a blog watcher that re-render when file changes."); - rootCommand.AddCommand(command); - - command.SetHandler(async context => - { - WebApplicationBuilder builder = WebApplication.CreateBuilder(); - - builder.Services.AddRazorComponents() - .AddInteractiveServerComponents(); - builder.Services.AddControllers(); - builder.AddYaeBlog(); - builder.AddWatcher(); - - WebApplication application = builder.Build(); - - application.UseStaticFiles(); - application.UseAntiforgery(); - application.UseYaeBlog(); - - application.MapRazorComponents() - .AddInteractiveServerRenderMode(); - application.MapControllers(); - - CancellationToken token = context.GetCancellationToken(); - await application.RunAsync(token); - }); - } - - private static void AddNewCommand(RootCommand rootCommand) - { - Command newCommand = new("new", "Create a new blog file and image directory."); - rootCommand.AddCommand(newCommand); - - Argument filenameArgument = new(name: "blog name", description: "The created blog filename."); - newCommand.AddArgument(filenameArgument); - - newCommand.SetHandler(async (file, _, _, essayScanService) => - { - BlogContents contents = await essayScanService.ScanContents(); - - if (contents.Posts.Any(content => content.FileName == file)) - { - Console.WriteLine("There exists the same title blog in posts."); - return; - } - - await essayScanService.SaveBlogContent(new BlogContent - { - FileName = file, - FileContent = string.Empty, - Metadata = new MarkdownMetadata { Title = file, Date = DateTime.Now } - }); - - Console.WriteLine($"Created new blog '{file}."); - }, filenameArgument, new BlogOptionsBinder(), new LoggerBinder(), - new EssayScanServiceBinder()); - } - - private static void AddListCommand(RootCommand rootCommand) - { - Command command = new("list", "List all blogs"); - rootCommand.AddCommand(command); - - command.SetHandler(async (_, _, essyScanService) => - { - BlogContents contents = await essyScanService.ScanContents(); - - Console.WriteLine($"All {contents.Posts.Count} Posts:"); - foreach (BlogContent content in contents.Posts.OrderBy(x => x.FileName)) - { - Console.WriteLine($" - {content.FileName}"); - } - - Console.WriteLine($"All {contents.Drafts.Count} Drafts:"); - foreach (BlogContent content in contents.Drafts.OrderBy(x => x.FileName)) - { - Console.WriteLine($" - {content.FileName}"); - } - }, new BlogOptionsBinder(), new LoggerBinder(), new EssayScanServiceBinder()); - } - - private static void AddScanCommand(RootCommand rootCommand) - { - Command command = new("scan", "Scan unused and not found images."); - rootCommand.AddCommand(command); - - Option removeOption = - new(name: "--rm", description: "Remove unused images.", getDefaultValue: () => false); - command.AddOption(removeOption); - - command.SetHandler(async (_, _, essayScanService, removeOptionValue) => - { - ImageScanResult result = await essayScanService.ScanImages(); - - if (result.UnusedImages.Count != 0) - { - Console.WriteLine("Found unused images:"); - Console.WriteLine("HINT: use '--rm' to remove unused images."); - } - - foreach (FileInfo image in result.UnusedImages) - { - Console.WriteLine($" - {image.FullName}"); - } - - if (removeOptionValue) - { - foreach (FileInfo image in result.UnusedImages) - { - image.Delete(); - } - } - - Console.WriteLine("Used not existed images:"); - - foreach (FileInfo image in result.NotFoundImages) - { - Console.WriteLine($" - {image.FullName}"); - } - }, new BlogOptionsBinder(), new LoggerBinder(), new EssayScanServiceBinder(), removeOption); - } - - private static void AddPublishCommand(RootCommand rootCommand) - { - Command command = new("publish", "Publish a new blog file."); - rootCommand.AddCommand(command); - - Argument filenameArgument = new(name: "blog name", description: "The published blog filename."); - command.AddArgument(filenameArgument); - - command.SetHandler(async (blogOptions, _, essayScanService, filename) => - { - BlogContents contents = await essayScanService.ScanContents(); - - BlogContent? content = (from blog in contents.Drafts - where blog.FileName == filename - select blog).FirstOrDefault(); - - if (content is null) - { - Console.WriteLine("Target blog does not exist."); - return; - } - - // 将选中的博客文件复制到posts - await essayScanService.SaveBlogContent(content, isDraft: false); - - // 复制图片文件夹 - DirectoryInfo sourceImageDirectory = - new(Path.Combine(blogOptions.Value.Root, "drafts", content.FileName)); - DirectoryInfo targetImageDirectory = - new(Path.Combine(blogOptions.Value.Root, "posts", content.FileName)); - - if (sourceImageDirectory.Exists) - { - targetImageDirectory.Create(); - foreach (FileInfo file in sourceImageDirectory.EnumerateFiles()) - { - file.CopyTo(Path.Combine(targetImageDirectory.FullName, file.Name), true); - } - - sourceImageDirectory.Delete(true); - } - - // 删除原始的文件 - FileInfo sourceBlogFile = new(Path.Combine(blogOptions.Value.Root, "drafts", content.FileName + ".md")); - sourceBlogFile.Delete(); - }, new BlogOptionsBinder(), - new LoggerBinder(), new EssayScanServiceBinder(), filenameArgument); - } -} diff --git a/YaeBlog/Components/App.razor b/YaeBlog/Components/App.razor deleted file mode 100644 index f6d881b..0000000 --- a/YaeBlog/Components/App.razor +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/YaeBlog/Components/Foonter.razor b/YaeBlog/Components/Foonter.razor deleted file mode 100644 index 8a5935b..0000000 --- a/YaeBlog/Components/Foonter.razor +++ /dev/null @@ -1,22 +0,0 @@ -
-
-

- 2021 - @(DateTimeOffset.Now.Year) © - - ,由 - - 驱动。 -

-
- - -
- -@code -{ - private string DotnetVersion => $".NET {Environment.Version}"; -} diff --git a/YaeBlog/Dockerfile b/YaeBlog/Dockerfile deleted file mode 100644 index 8883495..0000000 --- a/YaeBlog/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM mcr.microsoft.com/dotnet/aspnet:9.0 - -WORKDIR /app -COPY bin/Release/net9.0/publish/ ./ -COPY source/ ./source/ -COPY appsettings.json . - -ENTRYPOINT ["dotnet", "YaeBlog.dll", "serve"] diff --git a/YaeBlog/Extensions/ServiceCollectionExtensions.cs b/YaeBlog/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index fbd2a34..0000000 --- a/YaeBlog/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Markdig; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -namespace YaeBlog.Extensions; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddMarkdig(this IServiceCollection collection) - { - MarkdownPipelineBuilder builder = new(); - - builder.UseAdvancedExtensions(); - - collection.AddSingleton(_ => builder.Build()); - - return collection; - } - - public static IServiceCollection AddYamlParser(this IServiceCollection collection) - { - DeserializerBuilder deserializerBuilder = new(); - deserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance); - deserializerBuilder.IgnoreUnmatchedProperties(); - collection.AddSingleton(deserializerBuilder.Build()); - - SerializerBuilder serializerBuilder = new(); - serializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance); - collection.AddSingleton(serializerBuilder.Build()); - - return collection; - } -} diff --git a/YaeBlog/Extensions/WebApplicationBuilderExtensions.cs b/YaeBlog/Extensions/WebApplicationBuilderExtensions.cs deleted file mode 100644 index 078c89a..0000000 --- a/YaeBlog/Extensions/WebApplicationBuilderExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -using AngleSharp; -using Microsoft.Extensions.Options; -using YaeBlog.Abstraction; -using YaeBlog.Services; -using YaeBlog.Models; -using YaeBlog.Processors; - -namespace YaeBlog.Extensions; - -public static class WebApplicationBuilderExtensions -{ - public static WebApplicationBuilder AddYaeBlog(this WebApplicationBuilder builder) - { - builder.Services.Configure(builder.Configuration.GetSection(BlogOptions.OptionName)); - - builder.Services.AddHttpClient(); - - builder.Services.AddMarkdig(); - builder.Services.AddYamlParser(); - builder.Services.AddSingleton(_ => Configuration.Default); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(provider => - provider.GetRequiredService>().Value); - - return builder; - } - - public static WebApplicationBuilder AddServer(this WebApplicationBuilder builder) - { - builder.Services.AddHostedService(); - - return builder; - } - - public static WebApplicationBuilder AddWatcher(this WebApplicationBuilder builder) - { - builder.Services.AddTransient(); - builder.Services.AddHostedService(); - - return builder; - } -} diff --git a/YaeBlog/Layout/BlogLayout.razor b/YaeBlog/Layout/BlogLayout.razor deleted file mode 100644 index 82ab91a..0000000 --- a/YaeBlog/Layout/BlogLayout.razor +++ /dev/null @@ -1,44 +0,0 @@ -@inherits LayoutComponentBase - -@attribute [StreamRendering] - -
- - -
- @Body -
- - -
diff --git a/YaeBlog/Models/BlogContent.cs b/YaeBlog/Models/BlogContent.cs deleted file mode 100644 index 6ab5b7d..0000000 --- a/YaeBlog/Models/BlogContent.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace YaeBlog.Models; - -public class BlogContent -{ - public required string FileName { get; init; } - - public required MarkdownMetadata Metadata { get; init; } - - public required string FileContent { get; set; } - - public bool IsDraft { get; set; } = false; -} diff --git a/YaeBlog/Models/BlogContents.cs b/YaeBlog/Models/BlogContents.cs deleted file mode 100644 index 8a6f7d9..0000000 --- a/YaeBlog/Models/BlogContents.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Concurrent; - -namespace YaeBlog.Models; - -public sealed class BlogContents(ConcurrentBag drafts, ConcurrentBag posts) -{ - public ConcurrentBag Drafts { get; } = drafts; - - public ConcurrentBag Posts { get; } = posts; -} diff --git a/YaeBlog/Models/BlogEssay.cs b/YaeBlog/Models/BlogEssay.cs deleted file mode 100644 index 0af5cf6..0000000 --- a/YaeBlog/Models/BlogEssay.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace YaeBlog.Models; - -public class BlogEssay : IComparable -{ - public required string Title { get; init; } - - public required string FileName { get; init; } - - public required bool IsDraft { get; init; } - - public required DateTime PublishTime { get; init; } - - public required string Description { get; init; } - - public required uint WordCount { get; init; } - - public required string ReadTime { get; init; } - - public List Tags { get; } = []; - - public required string HtmlContent { get; init; } - - public BlogEssay WithNewHtmlContent(string newHtmlContent) - { - var essay = new BlogEssay - { - Title = Title, - FileName = FileName, - IsDraft = IsDraft, - PublishTime = PublishTime, - Description = Description, - WordCount = WordCount, - ReadTime = ReadTime, - HtmlContent = newHtmlContent - }; - essay.Tags.AddRange(Tags); - - return essay; - } - - public int CompareTo(BlogEssay? other) - { - if (other is null) - { - return -1; - } - - // 草稿文章应当排在前面 - if (IsDraft != other.IsDraft) - { - return IsDraft ? -1 : 1; - } - - return other.PublishTime.CompareTo(PublishTime); - } - - public override string ToString() - { - return $"{Title}-{PublishTime}"; - } -} diff --git a/YaeBlog/Models/BlogOptions.cs b/YaeBlog/Models/BlogOptions.cs deleted file mode 100644 index 0789f7a..0000000 --- a/YaeBlog/Models/BlogOptions.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace YaeBlog.Models; - -public class BlogOptions -{ - public const string OptionName = "Blog"; - - /// - /// 博客markdown文件的根目录 - /// - public required string Root { get; set; } - - /// - /// 博客正文的广而告之 - /// - public required string Announcement { get; set; } - - /// - /// 博客的起始年份 - /// - public required int StartYear { get; set; } - - /// - /// 博客的友链 - /// - public required List Links { get; set; } -} diff --git a/YaeBlog/Models/FriendLink.cs b/YaeBlog/Models/FriendLink.cs deleted file mode 100644 index e1f4640..0000000 --- a/YaeBlog/Models/FriendLink.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace YaeBlog.Models; - -/// -/// 友链模型类 -/// -public class FriendLink -{ - /// - /// 友链名称 - /// - public required string Name { get; set; } - - /// - /// 友链的简单介绍 - /// - public required string Description { get; set; } - - /// - /// 友链地址 - /// - public required string Link { get; set; } - - /// - /// 头像地址 - /// - public required string AvatarImage { get; set; } -} diff --git a/YaeBlog/Models/ImageScanResult.cs b/YaeBlog/Models/ImageScanResult.cs deleted file mode 100644 index 85f3cc7..0000000 --- a/YaeBlog/Models/ImageScanResult.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace YaeBlog.Models; - -public record struct ImageScanResult(List UnusedImages, List NotFoundImages); diff --git a/YaeBlog/Pages/About.razor b/YaeBlog/Pages/About.razor deleted file mode 100644 index 92aaf05..0000000 --- a/YaeBlog/Pages/About.razor +++ /dev/null @@ -1,79 +0,0 @@ -@page "/about" - - - 关于 - - -
-
-

关于

-
- -
- 把字刻在石头上!(・’ω’・) -
- -
-
-
-

关于我

-
- -
- 计算机科学与技术在读大学生,明光村幼儿园附属大学所属。正处于读书和失业的叠加态。 - 一般在互联网上使用初冬的朝阳或者jackfiled的名字活动。 - 都是ICP备案过的人了,网名似乎没有太大的用处( -
- -
- 主要是一个C#程序员,目前也在尝试写一点Rust。 - 总体上对于编程语言的态度是“大家都是我的翅膀.jpg”。 - 前后端分离的项目本当上手。 - 常常因为现实的压力而写一些C/C++。 - 对于Java和Go的评价很低。 - 日常使用ArchLinux。 -
- -
- 100%社恐。日常生活是宅在电脑前面自言自语。 - 兴趣活动是读书和看番,目前在玩原神和三角洲。 -
- -
- 常常被人批评没有梦想,这里就随便瞎编一下。 - 成为嵌入式工程师,修好桌面上的HoloCubic。 - 完成第一个不是课程设计的个人开源项目。 - 遇到能够搭伙过日子的人也算是一大梦想,虽然社恐人根本不知道从何开始的说, - 什么时候天上才能掉美少女? -
- -
- 公开的联系渠道是电子邮件。 - 也可以试试在各大平台搜索上面提到的名字。 -
-
- -
-
-

关于本站

-
- -
- 本站肇始于2021年下半年,在开始的两年中个人网站和博客是分别的两个网站,个人网站是裸HTML写的,博客是用 - Hexo渲染的。 -
- -
- 2024年,我们决定使用.NET技术完全重构两个网站,合二为一。虽然目前这个版本还是一个半成品,但是我们一定会努力的~(确信。 -
- -
- 2025年,我们将使用的样式库从Bootstrap迁移到Tailwind CSS,将现代的前端技术同Blazor结合起来。 -
-
-
-
- -@code { - -} diff --git a/YaeBlog/Pages/Index.razor b/YaeBlog/Pages/Index.razor deleted file mode 100644 index 1546c3a..0000000 --- a/YaeBlog/Pages/Index.razor +++ /dev/null @@ -1,59 +0,0 @@ -@page "/" - - - Ricardo's Index - - -
-
-
- Ricardo's Avatar -
- -
-
-
-
初冬的朝阳 (Ricardo Ren)
-
- -
-

a.k.a jackfiled

-
- -
-

世界很大,时间很长。

-
- -
-

- 平平无奇的计算机科学与技术学徒,连微小的贡献都没做。 -

-
-
-
-
- -
-

恕我不能亲自为您沏茶(?),还是非常欢迎您能够来到我的主页。

-
- -
-

- 如果您想四处看看,了解一下屏幕对面的人,可以在我的 看看。 - 如果您对于明光村幼儿园某附属技校的计算机教学感兴趣,您可以移步到 - , - 虽然这笔记我自己也木有看过。 - 如果您想批判一下我的代码,在 - 和 - - 都可以找到。 -

-

- 如果您真的很闲,也可以四处搜寻一下,也许存在着一些不为人知的彩蛋。 -

-
-
- -@code { - -} diff --git a/YaeBlog/Program.cs b/YaeBlog/Program.cs deleted file mode 100644 index e29050d..0000000 --- a/YaeBlog/Program.cs +++ /dev/null @@ -1,4 +0,0 @@ -using YaeBlog.Commands; - -YaeBlogCommand command = new(); -await command.RunAsync(args); diff --git a/YaeBlog/Services/EssayScanService.cs b/YaeBlog/Services/EssayScanService.cs deleted file mode 100644 index 1ef9b13..0000000 --- a/YaeBlog/Services/EssayScanService.cs +++ /dev/null @@ -1,209 +0,0 @@ -using System.Collections.Concurrent; -using System.Text.RegularExpressions; -using Microsoft.Extensions.Options; -using YaeBlog.Abstraction; -using YaeBlog.Core.Exceptions; -using YaeBlog.Models; -using YamlDotNet.Core; -using YamlDotNet.Serialization; - -namespace YaeBlog.Services; - -public partial class EssayScanService( - ISerializer yamlSerializer, - IDeserializer yamlDeserializer, - IOptions blogOptions, - ILogger logger) : IEssayScanService -{ - private readonly BlogOptions _blogOptions = blogOptions.Value; - - public async Task ScanContents() - { - ValidateDirectory(_blogOptions.Root, out DirectoryInfo drafts, out DirectoryInfo posts); - - return new BlogContents( - await ScanContentsInternal(drafts, true), - await ScanContentsInternal(posts, false)); - } - - public async Task SaveBlogContent(BlogContent content, bool isDraft = true) - { - ValidateDirectory(_blogOptions.Root, out DirectoryInfo drafts, out DirectoryInfo posts); - - FileInfo targetFile = isDraft - ? new FileInfo(Path.Combine(drafts.FullName, content.FileName + ".md")) - : new FileInfo(Path.Combine(posts.FullName, content.FileName + ".md")); - - if (!isDraft) - { - content.Metadata.Date = DateTime.Now; - } - - if (targetFile.Exists) - { - logger.LogWarning("Blog {} exists, overriding.", targetFile.Name); - } - - await using StreamWriter writer = targetFile.CreateText(); - - await writer.WriteAsync("---\n"); - await writer.WriteAsync(yamlSerializer.Serialize(content.Metadata)); - await writer.WriteAsync("---\n"); - - if (isDraft) - { - await writer.WriteLineAsync(""); - } - else - { - await writer.WriteAsync(content.FileContent); - } - } - - private async Task> ScanContentsInternal(DirectoryInfo directory, bool isDraft) - { - // 扫描以md结果的但是不是隐藏文件的文件 - IEnumerable markdownFiles = from file in directory.EnumerateFiles() - where file.Extension == ".md" && !file.Name.StartsWith('.') - select file; - - ConcurrentBag<(string, string)> fileContents = []; - - await Parallel.ForEachAsync(markdownFiles, async (file, token) => - { - using StreamReader reader = file.OpenText(); - fileContents.Add((file.Name, await reader.ReadToEndAsync(token))); - }); - - ConcurrentBag contents = []; - - await Task.Run(() => - { - foreach ((string filename, string content) in fileContents) - { - int endPos = content.IndexOf("---", 4, StringComparison.Ordinal); - if (!content.StartsWith("---") || endPos is -1 or 0) - { - logger.LogWarning("Failed to parse metadata from {}, skipped.", filename); - return; - } - - string metadataString = content[4..endPos]; - - try - { - MarkdownMetadata metadata = yamlDeserializer.Deserialize(metadataString); - logger.LogDebug("Scan metadata title: '{}' for {}.", metadata.Title, filename); - - contents.Add(new BlogContent - { - FileName = filename[..^3], Metadata = metadata, FileContent = content[(endPos + 3)..], - IsDraft = isDraft - }); - } - catch (YamlException e) - { - logger.LogWarning("Failed to parser metadata from {} due to {}, skipping", filename, e); - } - } - }); - - return contents; - } - - public async Task ScanImages() - { - BlogContents contents = await ScanContents(); - ValidateDirectory(_blogOptions.Root, out DirectoryInfo drafts, out DirectoryInfo posts); - - List unusedFiles = []; - List notFoundFiles = []; - - ImageScanResult draftResult = await ScanUnusedImagesInternal(contents.Drafts, drafts); - ImageScanResult postResult = await ScanUnusedImagesInternal(contents.Posts, posts); - - unusedFiles.AddRange(draftResult.UnusedImages); - notFoundFiles.AddRange(draftResult.NotFoundImages); - unusedFiles.AddRange(postResult.UnusedImages); - notFoundFiles.AddRange(postResult.NotFoundImages); - - return new ImageScanResult(unusedFiles, notFoundFiles); - } - - private static Task ScanUnusedImagesInternal(IEnumerable contents, - DirectoryInfo root) - { - ConcurrentBag unusedImage = []; - ConcurrentBag notFoundImage = []; - - Parallel.ForEach(contents, content => - { - MatchCollection result = ImagePattern.Matches(content.FileContent); - DirectoryInfo imageDirectory = new(Path.Combine(root.FullName, content.FileName)); - - Dictionary usedDictionary; - - if (imageDirectory.Exists) - { - usedDictionary = (from file in imageDirectory.EnumerateFiles() - select new KeyValuePair(file.FullName, false)).ToDictionary(); - } - else - { - usedDictionary = []; - } - - foreach (Match match in result) - { - string imageName = match.Groups[1].Value; - - FileInfo usedFile = imageName.Contains(content.FileName) - ? new FileInfo(Path.Combine(root.FullName, imageName)) - : new FileInfo(Path.Combine(root.FullName, content.FileName, imageName)); - - if (usedDictionary.TryGetValue(usedFile.FullName, out _)) - { - usedDictionary[usedFile.FullName] = true; - } - else - { - notFoundImage.Add(usedFile); - } - } - - foreach (KeyValuePair pair in usedDictionary.Where(p => !p.Value)) - { - unusedImage.Add(new FileInfo(pair.Key)); - } - }); - - return Task.FromResult(new ImageScanResult(unusedImage.ToList(), notFoundImage.ToList())); - } - - [GeneratedRegex(@"\!\[.*?\]\((.*?)\)")] - private static partial Regex ImagePattern { get; } - - private void ValidateDirectory(string root, out DirectoryInfo drafts, out DirectoryInfo posts) - { - root = Path.Combine(Environment.CurrentDirectory, root); - DirectoryInfo rootDirectory = new(root); - - if (!rootDirectory.Exists) - { - throw new BlogFileException($"'{root}' is not a directory."); - } - - if (rootDirectory.EnumerateDirectories().All(dir => dir.Name != "drafts")) - { - throw new BlogFileException($"'{root}/drafts' not exists."); - } - - if (rootDirectory.EnumerateDirectories().All(dir => dir.Name != "posts")) - { - throw new BlogFileException($"'{root}/posts' not exists."); - } - - drafts = new DirectoryInfo(Path.Combine(root, "drafts")); - posts = new DirectoryInfo(Path.Combine(root, "posts")); - } -} diff --git a/YaeBlog/YaeBlog.csproj b/YaeBlog/YaeBlog.csproj deleted file mode 100644 index 31b222f..0000000 --- a/YaeBlog/YaeBlog.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - net9.0 - enable - enable - - - - - - - - - - - - - - - - - - - - diff --git a/YaeBlog/appsettings.json b/YaeBlog/appsettings.json deleted file mode 100644 index 27635cb..0000000 --- a/YaeBlog/appsettings.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "Tailwind": { - "InputFile": "wwwroot/input.css", - "OutputFile": "wwwroot/output.css" - }, - "Blog": { - "Root": "source", - "Announcement": "博客锐意装修中,敬请期待!测试阶段如有问题还请海涵。", - "StartYear": 2021, - "Links": [ - { - "Name": "Ichirinko", - "Description": "这是个大哥", - "Link": "https://ichirinko.top", - "AvatarImage": "https://ichirinko-blog-img-1.oss-cn-shenzhen.aliyuncs.com/Pic_res/img/202209122110798.png" - }, - { - "Name": "志田千陽", - "Description": "日出多值得", - "Link": "https://zzachary.top/", - "AvatarImage": "https://zzachary.top/img/ztqy_hub928259802d192ff5718c06370f0f2c4_48203_300x0_resize_q75_box.jpg" - }, - { - "Name": "不会写程序的晨旭", - "Description": "一个普通大学生", - "Link": "https://chenxutalk.top", - "AvatarImage": "https://www.chenxutalk.top/img/photo.png" - }, - { - "Name": "万木长风", - "Description": "世界渲染中...", - "Link": "https://ryohai.fun", - "AvatarImage": "https://ryohai.fun/icon.jpg" - } - ] - } -} diff --git a/YaeBlog/docker-compose.yaml b/YaeBlog/docker-compose.yaml deleted file mode 100644 index a505b3d..0000000 --- a/YaeBlog/docker-compose.yaml +++ /dev/null @@ -1,13 +0,0 @@ -version: '3.8' - -services: - blog: - image: registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest - restart: unless-stopped - labels: - - "traefik.enable=true" - - "traefik.http.routers.blog.rule=Host(`rrricardo.top`) || Host(`www.rrricardo.top`)" - - "traefik.http.services.blog.loadbalancer.server.port=8080" - - "traefik.http.routers.blog.tls=true" - - "traefik.http.routers.blog.tls.certresolver=myresolver" - - "com.centurylinklabs.watchtower.enable=true" diff --git a/YaeBlog/package.json b/YaeBlog/package.json deleted file mode 100644 index 6a94197..0000000 --- a/YaeBlog/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "YaeBlog", - "version": "1.0.0", - "description": "", - "scripts": {}, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "tailwindcss": "^3.4.16" - } -} diff --git a/YaeBlog/pnpm-lock.yaml b/YaeBlog/pnpm-lock.yaml deleted file mode 100644 index dae0173..0000000 --- a/YaeBlog/pnpm-lock.yaml +++ /dev/null @@ -1,836 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - tailwindcss: - specifier: ^3.4.16 - version: 3.4.16 - -packages: - - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - camelcase-css@2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - didyoumean@1.2.2: - resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - - dlv@1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} - engines: {node: '>=14'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-core-module@2.16.0: - resolution: {integrity: sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==} - engines: {node: '>= 0.4'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jiti@1.21.6: - resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} - hasBin: true - - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - - postcss-import@15.1.0: - resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} - engines: {node: '>=14.0.0'} - peerDependencies: - postcss: ^8.0.0 - - postcss-js@4.0.1: - resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.4.21 - - postcss-load-config@4.0.2: - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - - postcss-nested@6.2.0: - resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.4.49: - resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} - engines: {node: ^10 || ^12 || >=14} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - read-cache@1.0.0: - resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - resolve@1.22.9: - resolution: {integrity: sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==} - hasBin: true - - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - tailwindcss@3.4.16: - resolution: {integrity: sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==} - engines: {node: '>=14.0.0'} - hasBin: true - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - yaml@2.6.1: - resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==} - engines: {node: '>= 14'} - hasBin: true - -snapshots: - - '@alloc/quick-lru@5.2.0': {} - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@jridgewell/gen-mapping@0.3.8': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.5.0': {} - - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 - - '@pkgjs/parseargs@0.11.0': - optional: true - - ansi-regex@5.0.1: {} - - ansi-regex@6.1.0: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.1: {} - - any-promise@1.3.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - arg@5.0.2: {} - - balanced-match@1.0.2: {} - - binary-extensions@2.3.0: {} - - brace-expansion@2.0.1: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - camelcase-css@2.0.1: {} - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - commander@4.1.1: {} - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - cssesc@3.0.0: {} - - didyoumean@1.2.2: {} - - dlv@1.1.3: {} - - eastasianwidth@0.2.0: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - fast-glob@3.3.2: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fastq@1.17.1: - dependencies: - reusify: 1.0.4 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - foreground-child@3.3.0: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - glob@10.4.5: - dependencies: - foreground-child: 3.3.0 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-core-module@2.16.0: - dependencies: - hasown: 2.0.2 - - is-extglob@2.1.1: {} - - is-fullwidth-code-point@3.0.0: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-number@7.0.0: {} - - isexe@2.0.0: {} - - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - jiti@1.21.6: {} - - lilconfig@3.1.3: {} - - lines-and-columns@1.2.4: {} - - lru-cache@10.4.3: {} - - merge2@1.4.1: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.1 - - minipass@7.1.2: {} - - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - - nanoid@3.3.8: {} - - normalize-path@3.0.0: {} - - object-assign@4.1.1: {} - - object-hash@3.0.0: {} - - package-json-from-dist@1.0.1: {} - - path-key@3.1.1: {} - - path-parse@1.0.7: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - pify@2.3.0: {} - - pirates@4.0.6: {} - - postcss-import@15.1.0(postcss@8.4.49): - dependencies: - postcss: 8.4.49 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.9 - - postcss-js@4.0.1(postcss@8.4.49): - dependencies: - camelcase-css: 2.0.1 - postcss: 8.4.49 - - postcss-load-config@4.0.2(postcss@8.4.49): - dependencies: - lilconfig: 3.1.3 - yaml: 2.6.1 - optionalDependencies: - postcss: 8.4.49 - - postcss-nested@6.2.0(postcss@8.4.49): - dependencies: - postcss: 8.4.49 - postcss-selector-parser: 6.1.2 - - postcss-selector-parser@6.1.2: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-value-parser@4.2.0: {} - - postcss@8.4.49: - dependencies: - nanoid: 3.3.8 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - queue-microtask@1.2.3: {} - - read-cache@1.0.0: - dependencies: - pify: 2.3.0 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - resolve@1.22.9: - dependencies: - is-core-module: 2.16.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - reusify@1.0.4: {} - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - signal-exit@4.1.0: {} - - source-map-js@1.2.1: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.1.0 - - sucrase@3.35.0: - dependencies: - '@jridgewell/gen-mapping': 0.3.8 - commander: 4.1.1 - glob: 10.4.5 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - - supports-preserve-symlinks-flag@1.0.0: {} - - tailwindcss@3.4.16: - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.6.0 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.2 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.21.6 - lilconfig: 3.1.3 - micromatch: 4.0.8 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.1.1 - postcss: 8.4.49 - postcss-import: 15.1.0(postcss@8.4.49) - postcss-js: 4.0.1(postcss@8.4.49) - postcss-load-config: 4.0.2(postcss@8.4.49) - postcss-nested: 6.2.0(postcss@8.4.49) - postcss-selector-parser: 6.1.2 - resolve: 1.22.9 - sucrase: 3.35.0 - transitivePeerDependencies: - - ts-node - - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - ts-interface-checker@0.1.13: {} - - util-deprecate@1.0.2: {} - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - - yaml@2.6.1: {} diff --git a/YaeBlog/source/posts/2021-final/1.png b/YaeBlog/source/posts/2021-final/1.png deleted file mode 100644 index 81022e2..0000000 --- a/YaeBlog/source/posts/2021-final/1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c35d649045aeb7b80a4d1b18290feabb367a3841d87b727d550dd06566801b6 -size 8340 diff --git a/YaeBlog/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.jpg b/YaeBlog/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.jpg deleted file mode 100644 index cc4461f..0000000 --- a/YaeBlog/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9a8ba878ab36ef4d9e540be7e8ee83e5c5d980e3968f5c7bea2917fded6ff83d -size 83539 diff --git a/YaeBlog/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.jpg b/YaeBlog/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.jpg deleted file mode 100644 index af7e125..0000000 --- a/YaeBlog/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7249babd4c5bdf499db30f92c20f18d4ff4fb4f0f7c2eff3afbf33bf8a37abe2 -size 122345 diff --git a/YaeBlog/source/posts/2022-summer-vacation/result1.png b/YaeBlog/source/posts/2022-summer-vacation/result1.png deleted file mode 100644 index 2d6effc..0000000 --- a/YaeBlog/source/posts/2022-summer-vacation/result1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e69a4cb12a942e16e289babf4c9448ae91725f66915501fdb300924c5c436c9b -size 3225055 diff --git a/YaeBlog/source/posts/2023-final/image-20240303165826486.png b/YaeBlog/source/posts/2023-final/image-20240303165826486.png deleted file mode 100644 index 0d82809..0000000 --- a/YaeBlog/source/posts/2023-final/image-20240303165826486.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:92bf121700d13fc738d5bcf46d9a814629dbf16b7ca289433506f6e8af70396c -size 118808 diff --git a/YaeBlog/source/posts/2024-final/image-20250115171809775.png b/YaeBlog/source/posts/2024-final/image-20250115171809775.png deleted file mode 100644 index d54b6f4..0000000 --- a/YaeBlog/source/posts/2024-final/image-20250115171809775.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6904fe34bcf46c1e9d672f278cedd54a59638cf2ea8d5a909acee9b436955387 -size 633715 diff --git a/YaeBlog/source/posts/big-homework/1.png b/YaeBlog/source/posts/big-homework/1.png deleted file mode 100644 index d062e81..0000000 --- a/YaeBlog/source/posts/big-homework/1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2d6244372d81d16899a7536cbd98dfd31c448ada89b899cd302738e3946d7544 -size 43864 diff --git a/YaeBlog/source/posts/build-blog-record/1.png b/YaeBlog/source/posts/build-blog-record/1.png deleted file mode 100644 index 504bdb6..0000000 --- a/YaeBlog/source/posts/build-blog-record/1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:43c5d93f5f893183366f51c021a5544d973ef8ff1d2ad59c05ad6dc8af6b5255 -size 45244 diff --git a/YaeBlog/source/posts/build-blog-record/2.png b/YaeBlog/source/posts/build-blog-record/2.png deleted file mode 100644 index aa05058..0000000 --- a/YaeBlog/source/posts/build-blog-record/2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d94c3bcc5c7838b4c12a28b96a212910101ee616dd9f581f77a33775ae29aae8 -size 494302 diff --git a/YaeBlog/source/posts/build-dotnet-from-source/image-20240824120646587.png b/YaeBlog/source/posts/build-dotnet-from-source/image-20240824120646587.png deleted file mode 100644 index a28a10c..0000000 --- a/YaeBlog/source/posts/build-dotnet-from-source/image-20240824120646587.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:df8608331d9104540c58c3710c96d95d6d569f248ec87a9a2ac13e3a05a54365 -size 189748 diff --git a/YaeBlog/source/posts/build-dotnet-from-source/image-20240824121425007.png b/YaeBlog/source/posts/build-dotnet-from-source/image-20240824121425007.png deleted file mode 100644 index 94f9a09..0000000 --- a/YaeBlog/source/posts/build-dotnet-from-source/image-20240824121425007.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:80b04aa95272ee4a22466a1168ff6dff4343cde50deb97d3a3ad4fcc7ed33793 -size 27785 diff --git a/YaeBlog/source/posts/build-dotnet-from-source/image-20240824134158262.png b/YaeBlog/source/posts/build-dotnet-from-source/image-20240824134158262.png deleted file mode 100644 index 651f00b..0000000 --- a/YaeBlog/source/posts/build-dotnet-from-source/image-20240824134158262.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c76e238cf1aefe3033f7e6a15b41c09ee323d63397d670ef35efadb4cf010a0 -size 149831 diff --git a/YaeBlog/source/posts/build-dotnet-from-source/image-20240824153514149.png b/YaeBlog/source/posts/build-dotnet-from-source/image-20240824153514149.png deleted file mode 100644 index 4e84f2f..0000000 --- a/YaeBlog/source/posts/build-dotnet-from-source/image-20240824153514149.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0403a9fa66547b6b597ff9ce568bcc6b2c45f57f83499ab14cf1c4405dfde730 -size 30117 diff --git a/YaeBlog/source/posts/build-dotnet-from-source/image-20240824214145759.png b/YaeBlog/source/posts/build-dotnet-from-source/image-20240824214145759.png deleted file mode 100644 index 2ca0b45..0000000 --- a/YaeBlog/source/posts/build-dotnet-from-source/image-20240824214145759.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:632da4d01c561cb4ec27e6130e3754b6ed82f6e212d6e6f2fefae1d115951419 -size 123971 diff --git a/YaeBlog/source/posts/c-include-problems/1.png b/YaeBlog/source/posts/c-include-problems/1.png deleted file mode 100644 index db120ad..0000000 --- a/YaeBlog/source/posts/c-include-problems/1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:401d690fe4ac6711decb03bfa27bf35d2045960b2f7bfdbb6964fc9bb0320a50 -size 11997 diff --git a/YaeBlog/source/posts/c-include-problems/2.png b/YaeBlog/source/posts/c-include-problems/2.png deleted file mode 100644 index 4db5f26..0000000 --- a/YaeBlog/source/posts/c-include-problems/2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:865e75431d84961d39bf653598807c82c09209b7236528dc91747b49fb41c361 -size 1538223 diff --git a/YaeBlog/source/posts/cncc-2024/image-20241102211959206.png b/YaeBlog/source/posts/cncc-2024/image-20241102211959206.png deleted file mode 100644 index 01a6ff9..0000000 --- a/YaeBlog/source/posts/cncc-2024/image-20241102211959206.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:76119fc2c4cd4f53679f800c41780727ef53a14bdefc594156d177cc49cb6bbb -size 2408090 diff --git a/YaeBlog/source/posts/cncc-2024/image-20241102212355390.png b/YaeBlog/source/posts/cncc-2024/image-20241102212355390.png deleted file mode 100644 index adc38ef..0000000 --- a/YaeBlog/source/posts/cncc-2024/image-20241102212355390.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9b30674b16f2d756eb8df19d33d2de459c194a8f1fb3bad99278adfa5c31e0dd -size 2712347 diff --git a/YaeBlog/source/posts/cncc-2024/image-20241102212536635.png b/YaeBlog/source/posts/cncc-2024/image-20241102212536635.png deleted file mode 100644 index 475ac3e..0000000 --- a/YaeBlog/source/posts/cncc-2024/image-20241102212536635.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:43e1c4a59ef2a75b57ce30d0ab780d2f381e74531233c648683bbd2f20136ddf -size 1544073 diff --git a/YaeBlog/source/posts/cncc-2024/image-20241102212738598.png b/YaeBlog/source/posts/cncc-2024/image-20241102212738598.png deleted file mode 100644 index 5fda1a0..0000000 --- a/YaeBlog/source/posts/cncc-2024/image-20241102212738598.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b03bf1455dd59785e21e88c5a106f040154f23977c968a68d5f9341e82fb5a1 -size 8559543 diff --git a/YaeBlog/source/posts/compile-mediapipe/2023-01-15-22-05-41-Screenshot_20230115_220521.png b/YaeBlog/source/posts/compile-mediapipe/2023-01-15-22-05-41-Screenshot_20230115_220521.png deleted file mode 100644 index b808741..0000000 --- a/YaeBlog/source/posts/compile-mediapipe/2023-01-15-22-05-41-Screenshot_20230115_220521.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b2a53c78a0e68f31fe6a2711de630db64aa1b7d81ca637802103a45102233d99 -size 99702 diff --git a/YaeBlog/source/posts/compile-mediapipe/2023-01-19-20-20-40-Screenshot_20230119_202008.png b/YaeBlog/source/posts/compile-mediapipe/2023-01-19-20-20-40-Screenshot_20230119_202008.png deleted file mode 100644 index 1fbd658..0000000 --- a/YaeBlog/source/posts/compile-mediapipe/2023-01-19-20-20-40-Screenshot_20230119_202008.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:15b1e6bc0d87f46937650d98703403b0536a7a61f30477418ae03a8b6361c252 -size 26745 diff --git a/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612184855300.png b/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612184855300.png deleted file mode 100644 index aa9d483..0000000 --- a/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612184855300.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:852fa4b226a15ff9e1f34cf13ef37426f4d6b7f37b0123150c99a76ec437c024 -size 23656 diff --git a/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612184949777.png b/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612184949777.png deleted file mode 100644 index 8af2134..0000000 --- a/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612184949777.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2ba0e36747d52eba53e756bac857f19da8c1859aba76c1819142940f6128e82a -size 75273 diff --git a/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612190426368.png b/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612190426368.png deleted file mode 100644 index 3aa6c00..0000000 --- a/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612190426368.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b8a608027cadcfd5e81468e7e52fa38430deed803e52c06fec8168ccb5bb3db5 -size 127594 diff --git a/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612192700169.png b/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612192700169.png deleted file mode 100644 index dc9e6e7..0000000 --- a/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612192700169.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0567f8196b2476e6fdcfd72c27bb1cb6cc2b444ff0f3264746d1db37eddc01af -size 46866 diff --git a/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612193301372.png b/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612193301372.png deleted file mode 100644 index d49b50b..0000000 --- a/YaeBlog/source/posts/computer-architecture-pipeline/image-20240612193301372.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d1ddd98fa16315aff0d31e4989879c730bad698d82920068592ca2d7788cb296 -size 127169 diff --git a/YaeBlog/source/posts/daily-linux-0/2023-01-12-13-28-38-Screenshot_20230112_132829.png b/YaeBlog/source/posts/daily-linux-0/2023-01-12-13-28-38-Screenshot_20230112_132829.png deleted file mode 100644 index 98b6c8e..0000000 --- a/YaeBlog/source/posts/daily-linux-0/2023-01-12-13-28-38-Screenshot_20230112_132829.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:92d829e0fd67280961dc68ed96477c788edfcf639186844ca91f35ed4ac10e74 -size 2874354 diff --git a/YaeBlog/source/posts/daily-linux-0/2023-01-12-13-36-45-Screenshot_20230112_133628.png b/YaeBlog/source/posts/daily-linux-0/2023-01-12-13-36-45-Screenshot_20230112_133628.png deleted file mode 100644 index 6a74eeb..0000000 --- a/YaeBlog/source/posts/daily-linux-0/2023-01-12-13-36-45-Screenshot_20230112_133628.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e57adb3e2bab147ce042cc51d72483ddb9842e3eda85442b0ad988d660a7012b -size 227688 diff --git a/YaeBlog/source/posts/daily-linux-2/image-20230702205919301.png b/YaeBlog/source/posts/daily-linux-2/image-20230702205919301.png deleted file mode 100644 index 0344fa1..0000000 --- a/YaeBlog/source/posts/daily-linux-2/image-20230702205919301.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:48a0c7ae799eca73de2ed8ed889b7ca6efd4891174ea746ee12bb97c4a2d444f -size 464377 diff --git a/YaeBlog/source/posts/daily-linux-3/Screenshot_20230904_144149.png b/YaeBlog/source/posts/daily-linux-3/Screenshot_20230904_144149.png deleted file mode 100644 index 3798ab2..0000000 --- a/YaeBlog/source/posts/daily-linux-3/Screenshot_20230904_144149.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0cd5c1c81e8faa1acdfeb3a3fbf65116970ee79fb811c08b23fcf91b1570d64d -size 3049270 diff --git a/YaeBlog/source/posts/daily-linux-4/Screenshot_20240309_115143.png b/YaeBlog/source/posts/daily-linux-4/Screenshot_20240309_115143.png deleted file mode 100644 index f2d5911..0000000 --- a/YaeBlog/source/posts/daily-linux-4/Screenshot_20240309_115143.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:56cb495f0a6f57c1b72eef008faa2b4ef9f572c2c2c2a8c6962cf6df5787fae7 -size 79552 diff --git a/YaeBlog/source/posts/daily-linux-4/cfd17cff0701a8e8c69fecf247f17fc1-1709963611271-2.jpg b/YaeBlog/source/posts/daily-linux-4/cfd17cff0701a8e8c69fecf247f17fc1-1709963611271-2.jpg deleted file mode 100644 index 0217928..0000000 --- a/YaeBlog/source/posts/daily-linux-4/cfd17cff0701a8e8c69fecf247f17fc1-1709963611271-2.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9195301a60278833cb62f6971a1cee5ac0a2aa0bc31770b79fe3c08872ac1ae6 -size 574570 diff --git a/YaeBlog/source/posts/daily-linux-4/image-20240309130329784.png b/YaeBlog/source/posts/daily-linux-4/image-20240309130329784.png deleted file mode 100644 index c2f9d16..0000000 --- a/YaeBlog/source/posts/daily-linux-4/image-20240309130329784.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:381b47a8cb243352ad1a9f57fab45f49cc1b8bd450aeeab4ed1a699359953669 -size 395168 diff --git a/YaeBlog/source/posts/daily-linux-4/image-20240309131750535.png b/YaeBlog/source/posts/daily-linux-4/image-20240309131750535.png deleted file mode 100644 index 85658f3..0000000 --- a/YaeBlog/source/posts/daily-linux-4/image-20240309131750535.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:938adf2b3037b870c6291878d9470a2b275f642e8a9fff739b23fada33a27f97 -size 157597 diff --git a/YaeBlog/source/posts/daily-linux-4/image-20240309134847166.png b/YaeBlog/source/posts/daily-linux-4/image-20240309134847166.png deleted file mode 100644 index e61c30a..0000000 --- a/YaeBlog/source/posts/daily-linux-4/image-20240309134847166.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2e76a2dd3c1fa0382410c0e980d42e0c27ed3a0e198036b4b5d65ee69122266c -size 38275 diff --git a/YaeBlog/source/posts/dotnet-performance-8/HeapsWhereNetObjectsLive.png b/YaeBlog/source/posts/dotnet-performance-8/HeapsWhereNetObjectsLive.png deleted file mode 100644 index f313abe..0000000 --- a/YaeBlog/source/posts/dotnet-performance-8/HeapsWhereNetObjectsLive.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:51d6acd5b3bb8134b3a56602328f948e391679492753934d7514a2ff10851d25 -size 21885 diff --git a/YaeBlog/source/posts/dotnet-performance-8/image-20240828135354598.png b/YaeBlog/source/posts/dotnet-performance-8/image-20240828135354598.png deleted file mode 100644 index aa123bb..0000000 --- a/YaeBlog/source/posts/dotnet-performance-8/image-20240828135354598.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6274b29f3b6dd75841ad0c36f2595403f9f326f2c90ed5ef062a3366a4f3fd9c -size 62003 diff --git a/YaeBlog/source/posts/dotnet-performance-8/image-20240828155556375.png b/YaeBlog/source/posts/dotnet-performance-8/image-20240828155556375.png deleted file mode 100644 index 763322f..0000000 --- a/YaeBlog/source/posts/dotnet-performance-8/image-20240828155556375.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f38ca49381035d50cea7549223f5b2b2f4ea27eebb286920b67b6f3784a8982b -size 38001 diff --git a/YaeBlog/source/posts/environment-setting/6.png b/YaeBlog/source/posts/environment-setting/6.png deleted file mode 100644 index b422cff..0000000 --- a/YaeBlog/source/posts/environment-setting/6.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4611461d3ae3af16c71f30bed786cd52928889d13e94cbd6e289676c201a352 -size 2794913 diff --git a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-06-36-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-06-36-image.png deleted file mode 100644 index 0903b38..0000000 --- a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-06-36-image.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2ed01520f4d7e6bde13d6afbe12e8739506bde28d4ae8658c757296e8f435c29 -size 111575 diff --git a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-20-46-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-20-46-image.png deleted file mode 100644 index bd4c2f9..0000000 --- a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-20-46-image.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:099b6ce419578d9016c7d9644bb6aef0fe3ad73f4402a29c3bf6feaca2914b3a -size 46251 diff --git a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-21-11-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-21-11-image.png deleted file mode 100644 index 463aac2..0000000 --- a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-21-11-image.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c8ec4f1a2c6c0184c86d8cce52e51e06f376f228c14307eac1e41c4ed6e58ed6 -size 37762 diff --git a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-24-26-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-24-26-image.png deleted file mode 100644 index 6ccee36..0000000 --- a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-13-24-26-image.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b4aa0365c268a0104d81cc4f0273254d3568f7dd48b05aefc51f7496f81c8512 -size 46392 diff --git a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-15-59-20-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-15-59-20-image.png deleted file mode 100644 index b6ddba3..0000000 --- a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-15-59-20-image.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:662bfafbae8857dc9a1349c6bf51b8b122a93ebd181ed50e0e4413204f1dc0d0 -size 10280 diff --git a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-15-59-42-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-15-59-42-image.png deleted file mode 100644 index e74abec..0000000 --- a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-15-59-42-image.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b9cae7637db04d556cded06019bab4454284b6a2c122382c865573ccd08df175 -size 10578 diff --git a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-16-00-10-image.png b/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-16-00-10-image.png deleted file mode 100644 index c5146bf..0000000 --- a/YaeBlog/source/posts/genshin-gacha-1/2022-12-31-16-00-10-image.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5fe30a67495ed6c97cafd4dcfd4432709f871bb86738596ca6a738ea5b4744fe -size 18066 diff --git a/YaeBlog/source/posts/heterogeneous-programming-model/83ee1d254d638536d0fb4197ff63e758.png b/YaeBlog/source/posts/heterogeneous-programming-model/83ee1d254d638536d0fb4197ff63e758.png deleted file mode 100644 index be2c3a8..0000000 --- a/YaeBlog/source/posts/heterogeneous-programming-model/83ee1d254d638536d0fb4197ff63e758.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:024500276e29f53f65759819e1d9b6c826aa09e91f50e8821d06dcf070560a18 -size 120181 diff --git a/YaeBlog/source/posts/heterogeneous-programming-model/9eb06d8be92ddef3db33e040163c67a7.png b/YaeBlog/source/posts/heterogeneous-programming-model/9eb06d8be92ddef3db33e040163c67a7.png deleted file mode 100644 index d1388d4..0000000 --- a/YaeBlog/source/posts/heterogeneous-programming-model/9eb06d8be92ddef3db33e040163c67a7.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5989d69e8fce9d83641034f103c8d4ef21a64f659d0e05660969819aa8bd6818 -size 160181 diff --git a/YaeBlog/source/posts/heterogeneous-programming-model/Screenshot_20241016_214139.png b/YaeBlog/source/posts/heterogeneous-programming-model/Screenshot_20241016_214139.png deleted file mode 100644 index 9b0bc73..0000000 --- a/YaeBlog/source/posts/heterogeneous-programming-model/Screenshot_20241016_214139.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:01d884fe4a429c28e53ee586ab6c7318d6dcc5b92c1891d68d6ce7af4645f431 -size 214725 diff --git a/YaeBlog/source/posts/heterogeneous-programming-model/Screenshot_20241016_214939.png b/YaeBlog/source/posts/heterogeneous-programming-model/Screenshot_20241016_214939.png deleted file mode 100644 index ac22d37..0000000 --- a/YaeBlog/source/posts/heterogeneous-programming-model/Screenshot_20241016_214939.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ec51d9872a4529d68c9b6afc9608d6b8007a57629ba5d117f7560527076d805 -size 82164 diff --git a/YaeBlog/source/posts/heterogeneous-programming-model/eab553f9e30d8d866a1ddd201b5e4c85.png b/YaeBlog/source/posts/heterogeneous-programming-model/eab553f9e30d8d866a1ddd201b5e4c85.png deleted file mode 100644 index 7483275..0000000 --- a/YaeBlog/source/posts/heterogeneous-programming-model/eab553f9e30d8d866a1ddd201b5e4c85.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f01cb683d410d1a45edc508a5ed3aeda6f1fcc208d0368a52504350cceb86c31 -size 130457 diff --git a/YaeBlog/source/posts/heterogeneous-programming-model/image-20241020142938110.png b/YaeBlog/source/posts/heterogeneous-programming-model/image-20241020142938110.png deleted file mode 100644 index 7cd6736..0000000 --- a/YaeBlog/source/posts/heterogeneous-programming-model/image-20241020142938110.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ba84669fd872bf19b665aa673dcd1f03868ed4b4621b9abd460090cb3bb93b9a -size 171981 diff --git a/YaeBlog/source/posts/heterogeneous-programming-model/image-20241020155656219.png b/YaeBlog/source/posts/heterogeneous-programming-model/image-20241020155656219.png deleted file mode 100644 index aa0ad1e..0000000 --- a/YaeBlog/source/posts/heterogeneous-programming-model/image-20241020155656219.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4b20c42dbe0bf434b4224d23dbedee1175d5dfb21e7bfdde314e1af5b2a9586 -size 194091 diff --git a/YaeBlog/source/posts/heterogeneous-programming-model/image-20241029123308139.png b/YaeBlog/source/posts/heterogeneous-programming-model/image-20241029123308139.png deleted file mode 100644 index 774102e..0000000 --- a/YaeBlog/source/posts/heterogeneous-programming-model/image-20241029123308139.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9675685943c6124ffdc70023f19c8d043c484d72f62866e6b4e87eca270e0366 -size 76309 diff --git a/YaeBlog/source/posts/heterogeneous-programming-model/image-20241029163654675.png b/YaeBlog/source/posts/heterogeneous-programming-model/image-20241029163654675.png deleted file mode 100644 index 2d64a1a..0000000 --- a/YaeBlog/source/posts/heterogeneous-programming-model/image-20241029163654675.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4e38a5bdee31a5ddf11329d6b72ca8b1b2b119f5988166477e8e0ff63a1fa40d -size 195933 diff --git a/YaeBlog/source/posts/heterogeneous-programming-model/image-20241103162259981.png b/YaeBlog/source/posts/heterogeneous-programming-model/image-20241103162259981.png deleted file mode 100644 index fa92f1c..0000000 --- a/YaeBlog/source/posts/heterogeneous-programming-model/image-20241103162259981.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ea27cccbb3e4de0b178fe7ea198f19e42603c90e254fca4356315998cf7dfce -size 38819 diff --git a/YaeBlog/source/posts/install-pytorch/1.png b/YaeBlog/source/posts/install-pytorch/1.png deleted file mode 100644 index 1ea1f95..0000000 --- a/YaeBlog/source/posts/install-pytorch/1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d1067846d8b27357d9c1be6d25d768137984074de8077445ed0347393bea76d6 -size 90203 diff --git a/YaeBlog/source/posts/install-pytorch/2.png b/YaeBlog/source/posts/install-pytorch/2.png deleted file mode 100644 index 73551bd..0000000 --- a/YaeBlog/source/posts/install-pytorch/2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:13206dc6972a3ce3f4b23d723f96b6ada5e34f87a051e9de446f2a07c375932d -size 38074 diff --git a/YaeBlog/source/posts/install-pytorch/3.png b/YaeBlog/source/posts/install-pytorch/3.png deleted file mode 100644 index 66ee198..0000000 --- a/YaeBlog/source/posts/install-pytorch/3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e813716f7b1e945e10942c55a7d5ea30ea57a0d374255ed110ed90ffea03633a -size 12042 diff --git a/YaeBlog/source/posts/install-pytorch/4.png b/YaeBlog/source/posts/install-pytorch/4.png deleted file mode 100644 index 062df7d..0000000 --- a/YaeBlog/source/posts/install-pytorch/4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0731b5398952c034de376b9db8980c3bd8a9d8a877a2bf0bb9991015926d80ee -size 39134 diff --git a/YaeBlog/source/posts/laptop-for-computer/c.png b/YaeBlog/source/posts/laptop-for-computer/c.png deleted file mode 100644 index c32ec4b..0000000 --- a/YaeBlog/source/posts/laptop-for-computer/c.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0f321d75e6ea913d68ab8a74206bd3a7dd086451fc69fde4339d39f8127de259 -size 139253 diff --git a/YaeBlog/source/posts/laptop-for-computer/clion.png b/YaeBlog/source/posts/laptop-for-computer/clion.png deleted file mode 100644 index 7b2dd2d..0000000 --- a/YaeBlog/source/posts/laptop-for-computer/clion.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:85f25d0fc05c7fc6e0c50dbda02d053f9127e3510161272f2645333d4b7a9aa2 -size 147636 diff --git a/YaeBlog/source/posts/laptop-for-computer/csharp.png b/YaeBlog/source/posts/laptop-for-computer/csharp.png deleted file mode 100644 index 988e1fd..0000000 --- a/YaeBlog/source/posts/laptop-for-computer/csharp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:415601b9824037f6d8c0f8b4759c0aa2afa4eafd02212c07e766727996ef30ee -size 151640 diff --git a/YaeBlog/source/posts/laptop-for-computer/web.png b/YaeBlog/source/posts/laptop-for-computer/web.png deleted file mode 100644 index 4468a3e..0000000 --- a/YaeBlog/source/posts/laptop-for-computer/web.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8b889b41f828d79416eb3ccb033fe8a0ab90953aaddd2cb36ea333c560e87195 -size 146745 diff --git a/YaeBlog/source/posts/llvm-naive-0/image-20240819213039409.png b/YaeBlog/source/posts/llvm-naive-0/image-20240819213039409.png deleted file mode 100644 index 39a5267..0000000 --- a/YaeBlog/source/posts/llvm-naive-0/image-20240819213039409.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ce8ce7601d7b1d041f25d781622657b2864be26f632bf30740f800e3f15ab63d -size 14787 diff --git a/YaeBlog/source/posts/llvm-naive-0/image-20240819213624927.png b/YaeBlog/source/posts/llvm-naive-0/image-20240819213624927.png deleted file mode 100644 index c9f1cdd..0000000 --- a/YaeBlog/source/posts/llvm-naive-0/image-20240819213624927.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bb82eae62bca2af0eb0c42c0e46a314a1663a161d5573ccd938a5c1d1916e67e -size 9215 diff --git a/YaeBlog/source/posts/llvm-naive-0/image-20240820221413791.png b/YaeBlog/source/posts/llvm-naive-0/image-20240820221413791.png deleted file mode 100644 index 5d1c75b..0000000 --- a/YaeBlog/source/posts/llvm-naive-0/image-20240820221413791.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18ff733fbd8f02ece52b3b7fd22f2df94b209f11db9af082125ad448bee6cf07 -size 81957 diff --git a/YaeBlog/source/posts/llvm-naive-0/image-20240825171858276.png b/YaeBlog/source/posts/llvm-naive-0/image-20240825171858276.png deleted file mode 100644 index 6f13bdf..0000000 --- a/YaeBlog/source/posts/llvm-naive-0/image-20240825171858276.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f198e93178db7a98fd7e2c0ffb3fc387d5b8793b37bed1cd0068d9f177a49ba6 -size 13107 diff --git a/YaeBlog/source/posts/minecraft-wayland/image-20240105212744116.png b/YaeBlog/source/posts/minecraft-wayland/image-20240105212744116.png deleted file mode 100644 index 4d2005d..0000000 --- a/YaeBlog/source/posts/minecraft-wayland/image-20240105212744116.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0308351e4a4a5387ea689fb5841e89578c5dd8e31a8f173c1842b4fa0819b537 -size 57285 diff --git a/YaeBlog/source/posts/minecraft-wayland/image-20240105213439528.png b/YaeBlog/source/posts/minecraft-wayland/image-20240105213439528.png deleted file mode 100644 index bbcb1e0..0000000 --- a/YaeBlog/source/posts/minecraft-wayland/image-20240105213439528.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b4d43e3eb2635648c755d8ab724a84571fb107f2b627495abdaaa7c058f313be -size 12603 diff --git a/YaeBlog/source/posts/minecraft-wayland/image-20240105213942445.png b/YaeBlog/source/posts/minecraft-wayland/image-20240105213942445.png deleted file mode 100644 index af1237d..0000000 --- a/YaeBlog/source/posts/minecraft-wayland/image-20240105213942445.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8379ee6733e6753da5ed2acddfcdff39b60e51d6f04c5e3c577e07ef6792bfc9 -size 153864 diff --git a/YaeBlog/source/posts/parser-combinator-performance/image-20240819140523087.png b/YaeBlog/source/posts/parser-combinator-performance/image-20240819140523087.png deleted file mode 100644 index a06cdf6..0000000 --- a/YaeBlog/source/posts/parser-combinator-performance/image-20240819140523087.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a34416d22a7b254db9904d2bb5ec5b7082a76df514c0316812f3477618f5c5eb -size 30728 diff --git a/YaeBlog/source/posts/parser-combinator/image-20240813214315576.png b/YaeBlog/source/posts/parser-combinator/image-20240813214315576.png deleted file mode 100644 index 3cae470..0000000 --- a/YaeBlog/source/posts/parser-combinator/image-20240813214315576.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c8565dc29005087be0fff55f3c56331cdcc70cc977ccbf9ef8ae047f90e5b8f3 -size 29655 diff --git a/YaeBlog/source/posts/parser-combinator/image-20240813220521028.png b/YaeBlog/source/posts/parser-combinator/image-20240813220521028.png deleted file mode 100644 index dc4ba0b..0000000 --- a/YaeBlog/source/posts/parser-combinator/image-20240813220521028.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:608d183ed3511d9c42ad2cd79f576c42609c99fb94a3014f8c841d5d95bf6a6d -size 164153 diff --git a/YaeBlog/source/posts/parser-combinator/image-20240813220530717.png b/YaeBlog/source/posts/parser-combinator/image-20240813220530717.png deleted file mode 100644 index 7f01189..0000000 --- a/YaeBlog/source/posts/parser-combinator/image-20240813220530717.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3db9d6b10173fd0bfa8dab9ae7c52b4f4ac151d2f2322cf34c055029e1560e69 -size 106064 diff --git a/YaeBlog/source/posts/program-design-introduction/1.png b/YaeBlog/source/posts/program-design-introduction/1.png deleted file mode 100644 index 6253516..0000000 --- a/YaeBlog/source/posts/program-design-introduction/1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6c1ff62a089d129cd7f1d62977829d2625627a758725aa5171af9bbddb61e1b0 -size 263019 diff --git a/YaeBlog/source/posts/program-design-introduction/2.png b/YaeBlog/source/posts/program-design-introduction/2.png deleted file mode 100644 index 240ecbf..0000000 --- a/YaeBlog/source/posts/program-design-introduction/2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:700eff5428a635793910f8058dd83c395f661f68497c0a5e7f780b03cc5c9081 -size 315995 diff --git a/YaeBlog/source/posts/program-design-introduction/3.png b/YaeBlog/source/posts/program-design-introduction/3.png deleted file mode 100644 index 64b21dc..0000000 --- a/YaeBlog/source/posts/program-design-introduction/3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e4a7ac08ebc2f1c524b9c0b1eefc97d5f9a855251f36cab44be81d901b1ea1f -size 686513 diff --git a/YaeBlog/source/posts/qt-learning/1.png b/YaeBlog/source/posts/qt-learning/1.png deleted file mode 100644 index e5e118a..0000000 --- a/YaeBlog/source/posts/qt-learning/1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:44d4b15d38af85412c9a7e502a502afaa9bac3c08b91db89207edfe90ee9ea1f -size 52016 diff --git a/YaeBlog/source/posts/question-in-install-vs-2019/1.png b/YaeBlog/source/posts/question-in-install-vs-2019/1.png deleted file mode 100644 index 97c6d07..0000000 --- a/YaeBlog/source/posts/question-in-install-vs-2019/1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6fd368bfe4a7defb24d72f069ca7a7bf8f69a417ac65e8dd7c526576cc4f14e -size 229076 diff --git a/YaeBlog/source/posts/rust-drop-stack-overflow/image-20241105181144993.png b/YaeBlog/source/posts/rust-drop-stack-overflow/image-20241105181144993.png deleted file mode 100644 index 93f1df8..0000000 --- a/YaeBlog/source/posts/rust-drop-stack-overflow/image-20241105181144993.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8cfa995fa0114dc3ca2a412da668d1e35e28072f06bb44b5abc8759c15d71275 -size 49002 diff --git a/YaeBlog/source/posts/rust-drop-stack-overflow/image-20241105181612954.png b/YaeBlog/source/posts/rust-drop-stack-overflow/image-20241105181612954.png deleted file mode 100644 index 6c6699d..0000000 --- a/YaeBlog/source/posts/rust-drop-stack-overflow/image-20241105181612954.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eba69761c69d220e829c43d064ba652b2bd5f7bf3a1f93c37c22a5e332b284a9 -size 81175 diff --git a/YaeBlog/source/posts/rust-drop-stack-overflow/image-20241105182036975.png b/YaeBlog/source/posts/rust-drop-stack-overflow/image-20241105182036975.png deleted file mode 100644 index 6c1317e..0000000 --- a/YaeBlog/source/posts/rust-drop-stack-overflow/image-20241105182036975.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4797186c7079795d37728da7227101aac6c8ce8b0da11af988f7ea5606191364 -size 43027 diff --git a/YaeBlog/source/posts/software-engineer/image-20240620211321957.png b/YaeBlog/source/posts/software-engineer/image-20240620211321957.png deleted file mode 100644 index d5e6f36..0000000 --- a/YaeBlog/source/posts/software-engineer/image-20240620211321957.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9feaffe8bb9531f3d7dcd49b836fe2615f43a32c1833a3e95f17ffd6c109db31 -size 134202 diff --git a/YaeBlog/source/posts/software-engineer/image-20240620212101864.png b/YaeBlog/source/posts/software-engineer/image-20240620212101864.png deleted file mode 100644 index adae8d9..0000000 --- a/YaeBlog/source/posts/software-engineer/image-20240620212101864.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:682ca3ab11829c8071189772060837063cbb62b5fcfde848aa71a15280cfa091 -size 190854 diff --git a/YaeBlog/source/posts/software-engineer/image-20240620214307906.png b/YaeBlog/source/posts/software-engineer/image-20240620214307906.png deleted file mode 100644 index 626f115..0000000 --- a/YaeBlog/source/posts/software-engineer/image-20240620214307906.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a244a26879b14d0dc35daf6218305d2fd96a822c77999bf36404904e6a9e394a -size 273406 diff --git a/YaeBlog/source/posts/software-engineer/image-20240620214739548.png b/YaeBlog/source/posts/software-engineer/image-20240620214739548.png deleted file mode 100644 index 50c2977..0000000 --- a/YaeBlog/source/posts/software-engineer/image-20240620214739548.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2fbce72d6f07c49345b1613b1ac4d9d0d7546fa8c5688c4dff923d115e5788ce -size 108027 diff --git a/YaeBlog/source/posts/software-engineer/image-20240620215022645.png b/YaeBlog/source/posts/software-engineer/image-20240620215022645.png deleted file mode 100644 index 8f1cec5..0000000 --- a/YaeBlog/source/posts/software-engineer/image-20240620215022645.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:feb20813948235d1dca857baf94dbf1212729b18db289212b55cacf9ae92dc74 -size 113775 diff --git a/YaeBlog/source/posts/software-engineer/image-20240620220155962.png b/YaeBlog/source/posts/software-engineer/image-20240620220155962.png deleted file mode 100644 index cef4bbe..0000000 --- a/YaeBlog/source/posts/software-engineer/image-20240620220155962.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b35bb450ffbe93bfd8d3d752fcce55b1bf7116f220e1eee5ba275097d67ce30 -size 194548 diff --git a/YaeBlog/source/posts/software-engineer/image-20240620224318982.png b/YaeBlog/source/posts/software-engineer/image-20240620224318982.png deleted file mode 100644 index 1f4f278..0000000 --- a/YaeBlog/source/posts/software-engineer/image-20240620224318982.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:faf61882914e3fab1b02847bedfc5482561e871644693d630efdc8b783b5e7d4 -size 119180 diff --git a/YaeBlog/source/posts/software-engineer/image-20240620224519243.png b/YaeBlog/source/posts/software-engineer/image-20240620224519243.png deleted file mode 100644 index a898b5d..0000000 --- a/YaeBlog/source/posts/software-engineer/image-20240620224519243.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e4334f70d148d512df0abea0eadc553a1f7d7dc9a923faac6fdb6ad581f63618 -size 190877 diff --git a/YaeBlog/source/posts/software-engineer/image-20240623160826903.png b/YaeBlog/source/posts/software-engineer/image-20240623160826903.png deleted file mode 100644 index cb255b4..0000000 --- a/YaeBlog/source/posts/software-engineer/image-20240623160826903.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4c375a4e5fe9910ee9263a1878c43c557e92cbf4d88a35af01e7b5d2e2d1d010 -size 155772 diff --git a/YaeBlog/source/posts/software-engineer/image-20240623162229404.png b/YaeBlog/source/posts/software-engineer/image-20240623162229404.png deleted file mode 100644 index 74c7155..0000000 --- a/YaeBlog/source/posts/software-engineer/image-20240623162229404.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:87403c9ee1023bf2442d7aa095083f12136d56872733dbe565202487293bea2a -size 330278 diff --git a/YaeBlog/source/posts/software-engineer/image-20240623163647935.png b/YaeBlog/source/posts/software-engineer/image-20240623163647935.png deleted file mode 100644 index f1c700d..0000000 --- a/YaeBlog/source/posts/software-engineer/image-20240623163647935.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:51743c989c50d02e37c103a243e1afc6d922f8f231feec802cdd400f331598e1 -size 146135 diff --git a/YaeBlog/source/posts/software-engineer/image-20240623170058679.png b/YaeBlog/source/posts/software-engineer/image-20240623170058679.png deleted file mode 100644 index 2b84b34..0000000 --- a/YaeBlog/source/posts/software-engineer/image-20240623170058679.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2a6b024c357a128ade7ebc5f6a853c236942c05ad9461b3ca7b781dc88b9158a -size 115178 diff --git a/YaeBlog/source/posts/spring-boot-custom-authorize/image-20230727175807814.png b/YaeBlog/source/posts/spring-boot-custom-authorize/image-20230727175807814.png deleted file mode 100644 index 29256a0..0000000 --- a/YaeBlog/source/posts/spring-boot-custom-authorize/image-20230727175807814.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:104460bc9297f7d1eb89d4bf1dc2d516bd371183c0169427af4ba47a9ac069d5 -size 34903 diff --git a/YaeBlog/source/posts/spring-boot-custom-authorize/image-20230727175955817.png b/YaeBlog/source/posts/spring-boot-custom-authorize/image-20230727175955817.png deleted file mode 100644 index 67320a9..0000000 --- a/YaeBlog/source/posts/spring-boot-custom-authorize/image-20230727175955817.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a5878ef72ef9f57be94a7a7fe5a02297131d87ae94b61c3a593d9d3311fff1ae -size 27551 diff --git a/YaeBlog/source/posts/vscode-in-browser/1.png b/YaeBlog/source/posts/vscode-in-browser/1.png deleted file mode 100644 index 0523623..0000000 --- a/YaeBlog/source/posts/vscode-in-browser/1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a74cd47bed9e46670d2b730a21fe550ea17609b552567f2a3ef8bbfc6db5a253 -size 37385 diff --git a/YaeBlog/source/posts/vscode-in-browser/2.png b/YaeBlog/source/posts/vscode-in-browser/2.png deleted file mode 100644 index c18e033..0000000 --- a/YaeBlog/source/posts/vscode-in-browser/2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d9d048021f921128b5daef4b4b884cb40d54367c6bb2840f0ddec722946b2824 -size 53525 diff --git a/YaeBlog/source/posts/whats-new-of-dotnet-8/AOTOptimizations4.png b/YaeBlog/source/posts/whats-new-of-dotnet-8/AOTOptimizations4.png deleted file mode 100644 index 713c7ac..0000000 --- a/YaeBlog/source/posts/whats-new-of-dotnet-8/AOTOptimizations4.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c307a43534321f2e35bf61d5ee5c29fe571e3f2dc9ae23c8d3f5ebf11e350e2 -size 312444 diff --git a/YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122100930849.png b/YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122100930849.png deleted file mode 100644 index e527a83..0000000 --- a/YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122100930849.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ad8ef505acf3d492b886658924ab6db8cf889ba5a43207c89f1bae77b52501b -size 76115 diff --git a/YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122101012416.png b/YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122101012416.png deleted file mode 100644 index d937496..0000000 --- a/YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122101012416.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e67ea7e7349e2805c6ef74c64a4121bd06ec50875ece402827ff40f640f2d2de -size 75303 diff --git a/YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122101142080.png b/YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122101142080.png deleted file mode 100644 index 5752bbd..0000000 --- a/YaeBlog/source/posts/whats-new-of-dotnet-8/image-20231122101142080.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:086d2b6fb4c74ab1986cd23ca4e2bd8d4b2922530a19415b463c80a6b5c5b5fe -size 74524 diff --git a/YaeBlog/source/posts/wsl-setup-csapp/1.png b/YaeBlog/source/posts/wsl-setup-csapp/1.png deleted file mode 100644 index 0b59632..0000000 --- a/YaeBlog/source/posts/wsl-setup-csapp/1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a392a744186b24dcb0b2fb78ad6d7ca2575df3be59fd229076159353394421c7 -size 377521 diff --git a/YaeBlog/source/posts/wsl-setup-csapp/2.png b/YaeBlog/source/posts/wsl-setup-csapp/2.png deleted file mode 100644 index 796829e..0000000 --- a/YaeBlog/source/posts/wsl-setup-csapp/2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0637be96b0f33b85cb415a185f6c46d07d46e9938a1b73deb50494b0b8ecdf11 -size 179838 diff --git a/YaeBlog/source/posts/wsl-setup-csapp/3.png b/YaeBlog/source/posts/wsl-setup-csapp/3.png deleted file mode 100644 index 0d835e1..0000000 --- a/YaeBlog/source/posts/wsl-setup-csapp/3.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:17b13932a250644377d0a4692ec15b68b0d469da2f5743fbb861e4e98f0e389f -size 106136 diff --git a/YaeBlog/tailwind.config.js b/YaeBlog/tailwind.config.js deleted file mode 100644 index 36dd647..0000000 --- a/YaeBlog/tailwind.config.js +++ /dev/null @@ -1,9 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ["**/*.razor", "**/*.cshtml", "**/*.html", "Processors/EssayStylesPostRenderProcessor.cs"], - theme: { - extend: {}, - }, - plugins: [], -} - diff --git a/YaeBlog/wwwroot/fonts/bootstrap-icons.woff b/YaeBlog/wwwroot/fonts/bootstrap-icons.woff deleted file mode 100644 index 51204d2..0000000 Binary files a/YaeBlog/wwwroot/fonts/bootstrap-icons.woff and /dev/null differ diff --git a/YaeBlog/wwwroot/fonts/bootstrap-icons.woff2 b/YaeBlog/wwwroot/fonts/bootstrap-icons.woff2 deleted file mode 100644 index cf957a1..0000000 --- a/YaeBlog/wwwroot/fonts/bootstrap-icons.woff2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:476adf42b40325098fcfa8b36ab3e769186bb4f6ce6a249753e2e1a9c22bf99e -size 130396 diff --git a/YaeBlog/wwwroot/fonts/fa-regular-400.ttf b/YaeBlog/wwwroot/fonts/fa-regular-400.ttf deleted file mode 100644 index d726723..0000000 --- a/YaeBlog/wwwroot/fonts/fa-regular-400.ttf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5d02dc9b858e3c85a794f87e379857f4fedc4e26cf15001714a9a0e0b1d2294d -size 68004 diff --git a/YaeBlog/wwwroot/fonts/fa-regular-400.woff2 b/YaeBlog/wwwroot/fonts/fa-regular-400.woff2 deleted file mode 100644 index d933b10..0000000 --- a/YaeBlog/wwwroot/fonts/fa-regular-400.woff2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2bccecf0bc7e96cd5ce4003abeb3ae9ee4a3d19158c4e6edfd2df32d2f0d5721 -size 25452 diff --git a/YaeBlog/wwwroot/fonts/fa-solid-900.ttf b/YaeBlog/wwwroot/fonts/fa-solid-900.ttf deleted file mode 100644 index ee7a766..0000000 --- a/YaeBlog/wwwroot/fonts/fa-solid-900.ttf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fbbf06d7437aa30f3cd44c968380193545a8fc3eadfb7ad897bbb101eefec5a2 -size 419720 diff --git a/YaeBlog/wwwroot/fonts/fa-solid-900.woff2 b/YaeBlog/wwwroot/fonts/fa-solid-900.woff2 deleted file mode 100644 index 20c927c..0000000 --- a/YaeBlog/wwwroot/fonts/fa-solid-900.woff2 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9fc85f3a4544ab0d570c7f8f9bbb88db8d92c359b2707580ea8b07c75673eae2 -size 156496 diff --git a/YaeBlog/wwwroot/globals.css b/YaeBlog/wwwroot/globals.css deleted file mode 100644 index ecbb546..0000000 --- a/YaeBlog/wwwroot/globals.css +++ /dev/null @@ -1,15 +0,0 @@ -@font-face { - font-family: "Font Awesome 6 Free"; - font-style: normal; - font-weight: 400; - font-display: block; - src: url(fonts/fa-regular-400.woff2) format("woff2"), url(fonts/fa-regular-400.ttf) format("truetype") -} - -@font-face { - font-family: "Font Awesome 6 Free"; - font-style: normal; - font-weight: 900; - font-display: block; - src: url(fonts/fa-solid-900.woff2) format("woff2"), url(fonts/fa-solid-900.ttf) format("truetype") -} diff --git a/YaeBlog/wwwroot/input.css b/YaeBlog/wwwroot/input.css deleted file mode 100644 index b5c61c9..0000000 --- a/YaeBlog/wwwroot/input.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/build.ps1 b/build.ps1 new file mode 100755 index 0000000..4b44f7f --- /dev/null +++ b/build.ps1 @@ -0,0 +1,158 @@ +#!pwsh + +[cmdletbinding()] +param( + [Parameter(Mandatory = $true, Position = 0, HelpMessage = "Specify the build target")] + [ValidateSet("publish", "compress", "build", "dev", "new", "watch", "serve")] + [string]$Target, + [string]$Essay, + [switch]$Compress, + [string]$Root = "source" +) + +begin { + if (($Target -eq "tailwind") -or ($Target -eq "build")) + { + # Handle tailwind specially. + return + } + + # Set the content root. + $fullRootPath = Join-Path $(Get-Location) $Root + if (-not (Test-Path $fullRootPath)) + { + Write-Error "Content root $fullRootPath not existed." + exit 1 + } + + Write-Host "Use content from" $fullRootPath + $env:BLOG__ROOT=$fullRootPath + + Write-Host "Building $Target..." + + if ($Target -eq "publish") + { + if ($Essay -eq "") + { + Write-Error "No publish target, please add with --essay argument." + exit 1 + } + } + + if ($Target -eq "new") + { + if ($Essay -eq "") + { + Write-Error "No new name, please add with --essay argument." + exit 1 + } + } + + # Set to the current location. + Push-Location src/YaeBlog +} + +process { + function Compress-Image + { + Write-Host "Compress image assets..." + dotnet run -- compress --dry-run + $confirm = Read-Host "Really compress images? (y/n)" + if ($confirm -notmatch "^[yY]$") + { + Write-Host "Not compress images." + return + } + + Write-Host "Do compress image..." + dotnet run -- compress + + dotnet run -- scan + $confirm = Read-Host "Really delete unused images? (y/n)" + if ($confirm -notmatch "^[yY]$") + { + Write-Host "Not delete images." + return + } + Write-Host "Do delete unused images.." + dotnet run -- scan --rm + } + + function Build-Image + { + $commitId = git rev-parse --short=10 HEAD + dotnet publish ./src/YaeBlog/YaeBlog.csproj -o out + Write-Host "Succeed to build blog appliocation." + podman build . -t ccr.ccs.tencentyun.com/jackfiled/blog --build-arg COMMIT_ID=$commitId ` + -f ./src/YaeBlog/Dockerfile + Write-Host "Succeed to build ccr.ccs.tencentyun.com/jackfiled/blog image." + } + + function Start-Develop { + Write-Host "Start tailwindcss and dotnet watch servers..." + $pnpmProcess = Start-Process pnpm "tailwindcss -i wwwroot/tailwind.css -o obj/Debug/net10.0/ClientAssets/tailwind.g.css -w" ` + -PassThru + + try + { + Write-Host "Started pnpm process exit? " $pnpmProcess.HasExited + Start-Process dotnet "watch -- serve" -PassThru | Wait-Process + } + finally + { + if ($pnpmProcess.HasExited) + { + Write-Error "pnpm process has exited!" + exit 1 + } + Write-Host "Kill tailwindcss and dotnet watch servers..." + $pnpmProcess | Stop-Process + } + } + + + switch ($Target) + { + "publish" { + Write-Host "Publish essay $Essay..." + dotnet run -- publish $Essay + + if ($Compress) + { + Compress-Image + } + break + } + "compress" { + Compress-Image + break + } + "build" { + Build-Image + break + } + "dev" { + Start-Develop + break + } + "new" { + dotnet run -- new $Essay + } + "watch" { + dotnet run -- watch + break + } + "serve" { + dotnet run -- serve + break + } + "list" { + dotnet run -- list + break + } + } +} + +end { + Pop-Location +} diff --git a/YaeBlog/source/drafts/.gitkeep b/source/drafts/.gitkeep similarity index 100% rename from YaeBlog/source/drafts/.gitkeep rename to source/drafts/.gitkeep diff --git a/YaeBlog/source/posts/.gitkeep b/source/posts/.gitkeep similarity index 100% rename from YaeBlog/source/posts/.gitkeep rename to source/posts/.gitkeep diff --git a/YaeBlog/source/posts/2021-final.md b/source/posts/2021-final.md similarity index 98% rename from YaeBlog/source/posts/2021-final.md rename to source/posts/2021-final.md index e29deb8..7f54bbd 100644 --- a/YaeBlog/source/posts/2021-final.md +++ b/source/posts/2021-final.md @@ -1,10 +1,12 @@ --- title: 2021年终总结 -date: 2022-01-12 16:27:19 +date: 2022-01-12T16:27:19.0000000 tags: - - 随笔 +- 杂谈 +- 年终总结 --- + 2021年已经过去,2022年已经来临。每每一年开始的时候,我都会展开一张纸或者新建一个文档,思量着又是一年时光,也该同诸大杂志一般,写几句意味深长的话语,怀念过去的时光,也祝福未来的自己。可往往脑海中已是三万字的长篇,落在笔头却又是一个字都没有了。 如今跨年的时候已经过去,朋友圈中已经不见文案的踪影,我也该重新提笔,细说自己2021年中做过的种种。 @@ -22,7 +24,7 @@ tags: 在前12年的学生生涯中,我们都在期待着这一次的暑假,以为在这个没有作业的假期里,我们就可以充分的享受人间的美好。可是,当时我们不知道,这人间的烦恼,可不止作业这一种,无论是突如其来的疫情导致开学延期,还是等待录取时的不安。 虽说在暑假时,拥有了自己的笔记本电脑,可是在高中三年屯下的游戏还是没有玩几个,看来我也是“喜加一”的受害者。虽然在高考后入坑了原神,但是假期间我并没有太过投入的玩。 暑假下定决心要好好的学一学,可是看着我gitee上暑假期间那稀疏的提交,我就知道我又摸了一个暑假的鱼。 -![gitee贡献](./2021-final/1.png) +![gitee贡献](./2021-final/1.webp) 即使我想写的很多项目都没有被扎实的推进下来,但是学习的一些的C语言还是让我受益匪浅。 现在看来,这个假期真是,**学也没有学好,耍也没有耍好**的典型。 diff --git a/source/posts/2021-final/1.webp b/source/posts/2021-final/1.webp new file mode 100644 index 0000000..0f96b20 --- /dev/null +++ b/source/posts/2021-final/1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:435af818a4675aad44016e5c390c101ede156d1dfa445e851de32bdfb342d915 +size 2982 diff --git a/YaeBlog/source/posts/2022-final.md b/source/posts/2022-final.md similarity index 97% rename from YaeBlog/source/posts/2022-final.md rename to source/posts/2022-final.md index f9c31f8..fa58ecc 100644 --- a/YaeBlog/source/posts/2022-final.md +++ b/source/posts/2022-final.md @@ -1,11 +1,13 @@ --- title: 2022年终总结 +date: 2022-12-30T14:58:12.0000000 tags: - - 随笔 -date: 2022-12-30 14:58:12 +- 杂谈 +- 年终总结 --- + 2022是困难的一年。我们需要为2023年做好准备。 @@ -56,11 +58,11 @@ date: 2022-12-30 14:58:12 小小的总结一下:2022年可以算得上是一事无成的一年,还搞砸了不少的事情。在写代码上进展有限,成绩上大幅倒退,说好的六级英语和大学物理竞赛都没有参加,在年末应对疫情进展的时候更是把“不知所措”这个成语诠释的淋漓尽致。 -![](./2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.jpg) +![](./2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.webp) 关于今年的人际交往和社会关系,我愿意用QQ2022年年终总结中的一张截屏来总结,这张图片透漏出一种无可救药的悲伤。 -![](./2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.jpg) +![](./2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.webp) ## 展望 diff --git a/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.webp b/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.webp new file mode 100644 index 0000000..68361ff --- /dev/null +++ b/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e697061aab4e88f7b3eae2b91f9d2c9a9291da95d54f5bd4bfc0a1f6825cc9f +size 249208 diff --git a/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.webp b/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.webp new file mode 100644 index 0000000..d3fa901 --- /dev/null +++ b/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f663fe26e66a2498f12b0544993279d1458e165dde9c4c9046fc77d622ef5a9d +size 768344 diff --git a/YaeBlog/source/posts/2022-summer-vacation.md b/source/posts/2022-summer-vacation.md similarity index 97% rename from YaeBlog/source/posts/2022-summer-vacation.md rename to source/posts/2022-summer-vacation.md index 3fab150..3b53c96 100644 --- a/YaeBlog/source/posts/2022-summer-vacation.md +++ b/source/posts/2022-summer-vacation.md @@ -1,11 +1,11 @@ --- title: 2022年暑假碎碎念 +date: 2022-08-22T15:39:13.0000000 tags: - - 随笔 -typora-root-url: 2022-summer-vacation -date: 2022-08-22 15:39:13 +- 杂谈 --- + 在8个月的漫长寒假的最后两个月,~~也就是俗称的暑假中~~,我都干了些什么? @@ -32,7 +32,7 @@ date: 2022-08-22 15:39:13 - 下定决定要参加下一学期的物理竞赛,但是在听了讲座之后直接决定开学再开始学习,~~我知道我在家没法学习,俗称开摆~~ - 又捡起了`Blender`,并在[Github](https://github.com/tanjian1998/bupt_minecraft)上找到了伟大的前辈们在`Minecraft`里复刻的老校区,希望能用`Blender`渲染几张图当作桌面。 -![唯一的一张成品](result1.png) +![唯一的一张成品](result1.webp) > 在此感谢所有为此付出过汗水的前辈们,让我这个即将搬入老校区的萌新能提前一睹老校区的风采。 diff --git a/source/posts/2022-summer-vacation/result1.webp b/source/posts/2022-summer-vacation/result1.webp new file mode 100644 index 0000000..91b8e78 --- /dev/null +++ b/source/posts/2022-summer-vacation/result1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15a99f1ad3aa6b4edb7d3567b7f2dafb287ff457c0492618e6c37df0cde3cc43 +size 137362 diff --git a/YaeBlog/source/posts/2023-final.md b/source/posts/2023-final.md similarity index 98% rename from YaeBlog/source/posts/2023-final.md rename to source/posts/2023-final.md index 1cc46f6..b06d0e6 100644 --- a/YaeBlog/source/posts/2023-final.md +++ b/source/posts/2023-final.md @@ -1,10 +1,12 @@ --- title: 2023年年终总结 +date: 2024-02-29T20:18:19.0000000 tags: - - 随笔 -date: 2024-2-29 20:18:19 +- 杂谈 +- 年终总结 --- + 虽然2023年已经过去了两个月,但是年终总结还是要发的。 @@ -43,7 +45,7 @@ date: 2024-2-29 20:18:19 2023年最令我吃惊的事情是我刷B站的时长: -![image-20240303165826486](2023-final/image-20240303165826486.png) +![image-20240303165826486](2023-final/image-20240303165826486.webp) 容易计算得出,我一共看了64天的B站,接近六分之一的时间都在看。虽然我确实有着在干活的时候黑听B站和把B站当作音乐播放器的习惯,但是这个时间未免有点太长了。下一年一定要在这个方面做出一定的改变,将更多的时间放在看书上面去,~~虽然写这句话的时候我就在黑听B站~~。 diff --git a/source/posts/2023-final/image-20240303165826486.webp b/source/posts/2023-final/image-20240303165826486.webp new file mode 100644 index 0000000..43289ac --- /dev/null +++ b/source/posts/2023-final/image-20240303165826486.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:614e92e6c52bbfc1e87811dd0c283abe1da70cee41e143b4398cff0a6bacdad2 +size 72936 diff --git a/YaeBlog/source/posts/2024-final.md b/source/posts/2024-final.md similarity index 99% rename from YaeBlog/source/posts/2024-final.md rename to source/posts/2024-final.md index 1e12ad1..56411f3 100644 --- a/YaeBlog/source/posts/2024-final.md +++ b/source/posts/2024-final.md @@ -6,6 +6,7 @@ tags: - 年终总结 --- + 欸,年终总结难道不是应该在新年当天发出吗,什么已经是新年第三天了?! 然而年末偶遇流感病毒,头疼脑热强如怪物,拼尽全力也无法战胜。 @@ -70,7 +71,7 @@ tags: 不过我的B站观看时长再度增长30%,这好吗,这不好,~~有这么多时间刷B站,鬼知道你匆匆在哪了~~。 -![image-20250115171809775](./2024-final/image-20250115171809775.png) +![image-20250115171809775](./2024-final/image-20250115171809775.webp) ### 未来 diff --git a/source/posts/2024-final/image-20250115171809775.webp b/source/posts/2024-final/image-20250115171809775.webp new file mode 100644 index 0000000..74fdb8b --- /dev/null +++ b/source/posts/2024-final/image-20250115171809775.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c89ea64941bb737a304acca227436c41cb0eb9e0764a33d967888135265ccddd +size 38728 diff --git a/YaeBlog/source/posts/archlinux-sop.md b/source/posts/archlinux-sop.md similarity index 100% rename from YaeBlog/source/posts/archlinux-sop.md rename to source/posts/archlinux-sop.md diff --git a/YaeBlog/source/posts/aspnet-authorization.md b/source/posts/aspnet-authorization.md similarity index 100% rename from YaeBlog/source/posts/aspnet-authorization.md rename to source/posts/aspnet-authorization.md diff --git a/YaeBlog/source/posts/aspnet-authorization/middleware-pipeline.svg b/source/posts/aspnet-authorization/middleware-pipeline.svg similarity index 100% rename from YaeBlog/source/posts/aspnet-authorization/middleware-pipeline.svg rename to source/posts/aspnet-authorization/middleware-pipeline.svg diff --git a/source/posts/aspnetcore-swa.md b/source/posts/aspnetcore-swa.md new file mode 100644 index 0000000..6244f7b --- /dev/null +++ b/source/posts/aspnetcore-swa.md @@ -0,0 +1,224 @@ +--- +title: ASP.NET Core中的静态Web资产 +date: 2026-01-04T16:36:36.5629759+08:00 +tags: +- 技术笔记 +- dotnet +- ASP.NET Core +--- + + +Web服务器应该如何扫描与提供静态Web文件,尤其是在考虑到缓存、压缩的情况下,还需要正确的处理开发环境和部署环境之间的差异?让我们来看看ASP.NET Core是如何处理这个问题的。以及如何将通过其他工具(例如`pnpm`)生成的前端资产文件集成到ASP.NET Core中。 + + + +### 引言——Blazor开发中的静态Web资源 + +Blazor是ASP.NET Core中~~新推出的~~Web应用程序开发框架,通过一系列精巧的设计实现了使用HTML和C#编写运行在浏览器中的应用程序,避免了使用丑陋的JavaScript。但是现代的前端开发生态几乎都建立在JavaScript之上,尤其是考虑在JavaScript在很长的一段时间都是浏览器唯一支持的脚本语言,在Blazor项目开发的过程中必然会遇到一些只能编写JavaScript才能解决的问题。同时,一系列的现代前端工具,例如[tailwindcss](https://tailwindcss.com/),提供了更加优秀的前端开发体验,但是这些都基于NodeJS和NPM等前端工具。以上的前端生态引入了一个问题,如何在MSBuild驱动的Blazor应用构建流程中自然地运行前端工具链和ASP.NET Core支持的服务器部署生成的静态资源? + +Blazor目前提供了一个入口简洁但是功能丰富的静态Web资源提供功能。在使用默认应用目录的情况下,项目将会提供一个`wwwroot`文件夹,这个文件夹中的内容将可以从`/`直接寻址。为了提升前端静态文件的使用体验,该文件夹下的文件将会经过一个复杂的管道: + +- 在构建扫描到这些资产文件之后,MSBuild将会给静态文件加上内容指纹,以防止重复使用旧文件。资源还会被压缩,以减少资产交付的时间。 +- 在运行时,所发现的资产文件将会作为终结点公开,并添加上合适的缓存头和内容类型头。在设置`ETtag`,`Last-Modified`和`Content-Type`头之后,浏览器将可以合理的缓存这些静态文件直到应用更新。 + +该静态文件功能还需要适应应用程序的部署状态: + +- 在开发时,或者说运行`dotnet run`和`dotnet build`时,该功能需要将对应的静态文件终结点URL映射到磁盘上存储的实际静态文件上,就像它们实际上就在`wwwroot`文件夹中一样。考虑到实际上开发过程中会用到Blazor内部的资产文件`blazor.web.js`,引用项目中的资产文件等等,这实际上一个相当复杂的检测-映射流程。 +- 在发布时,或者说运行`dotnet publish`时,该功能需要收集所有需要的静态文件并复制到最终发布文件夹的`wwwroot`文件夹之下。 + +### Microsoft.AspNetCore.ClientAssets + +在默认的应用模板下,如果需要使用其他的现代前端工具生成静态资产文件,最简单的方法就是手动或者编写MSBuild目标(Target)生成资产文件并放在`wwwroot`文件夹中。但是这个方法存在着如下几个问题: + +- 开发者需要编写 MSBuild 目标(targets)来调用他们的工具。 +- 开发者通常没有在构建过程的恰当时机执行其自定义目标。 +- 开发者将工具生成的输出文件放入应用的 wwwroot 文件夹中,这会导致这些文件在下一次构建时被误认为是输入文件。 +- 开发者没有为这些工具正确声明输入和输出依赖,导致即使输出文件已是最新,工具仍会重复运行,从而增加构建耗时。 + +面对这些问题,M$提供了一个Alpha状态的库`Microsoft.AspNetCore.ClientAssets`来解决这个问题。不幸的是,这个库已经因为年久失修(上一次[更新](https://github.com/aspnet/AspLabs/pull/572)是在3年前,引入对于.NET 7的支持),在.NET 9引入新的静态资产部署管线之后,使用会直接报错了。 + +^^ 相关的Issues链接:[#38445](https://github.com/dotnet/aspnetcore/issues/38445),[#62925](https://github.com/dotnet/aspnetcore/issues/62925) + +为了良好地解决如上的问题,我们需要首先了解一下ASP.NET Core中静态资产文件的构建和部署过程。 + +### StaticWebAssetsSdk + +在.NET中,构建静态资产文件的相关代码在[dotnet/sdk](https://github.com/dotnet/sdk)仓库中,称作`StaticWebAssetsSdk`。 + +静态 Web 资源会接管应用程序 wwwroot 文件夹中的内容项,并全面管理这些内容。在开发过程中,系统会生成一个 JSON 清单(manifest),其中包含以下信息: + +- 版本号(version number):标识清单格式的版本。 + +- 清单内容的哈希值(hash):用于判断清单内容是否发生变化。 + +- 库的包 ID(library package id):用于区分当前项目与其他项目所提供的资源。 + +- 库的资源基础路径(asset base path):在将其他库的路径应用“发现模式”时,用于确定要添加的基础路径。 + +- 清单模式(manifest mode):定义来自特定项目的资源在构建和发布时应如何处理。 + +- 相关项目清单及其哈希值的列表:用于判断自清单生成以来,项目引用是否发生变化,或是否有清单被更新。 + +- “发现模式”(discovery patterns)列表:用于在清单构建完成后,有选择性地在运行时提供某些资源。例如,可以使用如下模式: + + ```json + { "Path": "/Pages", "BasePath": "_content/Something", "Pattern": "**/*.js" } + ``` + + 表示仅提供该目录下扩展名为`js`的文件。如果有人添加了图片或其他文件,它们将不会被提供。(这一点很重要,因为这些文件并不符合任何资源规则,也不会包含在发布输出目录中。) + +- 构建/发布过程中生成的静态 Web 资源列表。 + +系统会生成两套清单:**构建清单(build manifest)** 和 **发布清单(publish manifest)**: + +- **构建清单**在构建过程中生成,用于开发阶段,使资源表现得如同它们属于应用程序本身。 +- **发布清单**在发布过程中生成,记录了发布阶段对资源执行的所有转换操作。 + +资源可以在构建阶段或发布阶段定义,并可在任意阶段被标记为“仅构建”或“仅发布”。例如,你可以有两个文件:一个用于开发,一个用于发布,但它们都需要通过相同的 URL 路径提供服务。`service-worker.js` 就是一个典型例子。 + +**构建时清单**由项目中发现的资源以及来自被引用项目和包的资源共同组成。 +**发布清单**则以构建清单为基础,过滤掉仅用于构建的文件,并包含在发布过程中对这些文件执行的所有转换(如链接、打包、压缩等)。 + +这种机制使得在发布阶段可以执行如链接(Linking)、打包(Bundling)、压缩(Compression)等优化操作。被引用的项目也会生成自己的发布清单,其内容会在发布过程中与当前项目的清单合并。同时,在发布过程中,我们仍会保留被引用项目的原始构建清单,以便应用程序可以选择忽略被引用项目的发布资源,并对整个依赖传递闭包中的资源执行全局优化。例如,一个类库在发布时可能生成一个压缩后的 JS 包,而主应用可以选择不使用多个独立的包,而是收集所有原始构建阶段的资源,生成一个统一的包。通常情况下,构建清单和发布清单内容相同,除非存在仅在发布阶段才应用的转换。 + +每份清单中会列出在构建/发布过程中生成或计算出的所有资源及其属性。这些属性包括: + +- **Identity**:资源的唯一标识(文件的完整路径)。 +- **SourceType**:资源类型('Discovered', 'Computed', 'Project', 'Package')。 +- **ContentRoot**:开发阶段资源暴露的原始路径。 +- **BasePath**:资源暴露的基础路径。 +- **RelativePath**:资源的相对路径。 +- **AssetKind**:资源用途('Build', 'Publish', 'All'),由 `CopyToOutputDirectory` / `CopyToPublishDirectory` 推断得出。 +- **AssetMode**:资源作用范围('CurrentProject', 'Reference', 'All')。 +- **AssetRole**:资源角色('Primary', 'Related', 'Alternative')。 +- **AssetMergeSource**:当资源被嵌入到其他 TFM(目标框架)时的来源。 +- **AssetMergeBehavior**:当同一 TFM 中出现资源冲突时的合并行为。 +- **RelatedAsset**:当前资源所依赖的主资源的 Identity。 +- **AssetTraitName**:区分相关或替代资源与主资源的特征名称(如语言、编码格式等)。 +- **AssetTraitValue**:该特征的具体值。 +- **CopyToBuildDirectory**:与 Content 项一致(如 PreserveNewest、Always)。 +- **CopyToPublishDirectory**:与 Content 项一致。 +- **OriginalItemSpec**:定义该资源的原始项规范。 + +关于资源在不同场景下的使用(作为主项目的一部分,或作为被引用项目的一部分),有三种可能的选项: + +- **All**:资源在所有情况下都应被使用。 +- **Root**:资源仅在当前项目作为主项目构建时使用。 +- **Reference**:资源仅在当前项目被其他项目引用时使用。 + +例如,CSS 隔离(CSS isolation)生成的两个包: + +- `<>.styles.css` 是 **Root** 资源,仅在作为主项目时使用。 +- `<>.lib.bundle.css` 是 **Reference** 资源,仅在被其他项目引用时使用。 + +除了上述三种使用模式,项目还需定义其在构建和发布过程中如何处理清单中的文件。对此有三种模式: + +- **Default**:项目在发布时将所有内容复制到发布输出目录,但当被其他项目引用时不做任何操作,而是期望引用方负责处理静态 Web 资源的发布。 + → 通常用于类库(class libraries)。 +- **Root**:项目被视为静态 Web 资源的“根”,即使被引用,其资源也应像主项目一样被处理(例如,不复制传递依赖资源,而只复制 Root 资源)。 + → 用于如 Blazor WebAssembly 托管项目(被 ASP.NET Core 主机项目引用,但资源应视为根项目)。 +- **Isolated**:与 Root 类似,但引用项目完全不知道静态 Web 资源的存在;当前项目会自行在发布时设置处理程序,将资源复制到正确位置。 + → 用于如 Blazor 桌面应用,将静态 Web 资源自动纳入 `GetCopyToPublishDirectoryItems`,使引用方无需了解静态 Web 资源机制。 + +关于资源类型,静态 Web 资源可分为四类: + +- **Discovered assets**:从项目中已有项(如 Content、None 等)中发现的资源。 +- **Computed assets**:在构建过程中生成、需要在构建时复制到最终位置的资源。 +- **Project**:来自被引用项目的资源。当合并被引用项目的清单时,其 Discovered 和 Computed 资源会转换为 Project 类型。 +- **Package**:来自被引用 NuGet 包的资源。 + +关于资源角色(Asset Role),有三种: + +- **Primary(主资源)**:表示可通过其相对路径直接访问的资源。大多数资源属于此类。 +- **Related(相关资源)**:表示与另一个资源相关,但两者都可通过各自的相对路径独立访问。 +- **Alternative(替代资源)**:表示是另一个资源的替代形式,例如预压缩版本或不同格式版本。通常应通过与主资源相同的相对路径提供(具体实现由运行时决定)。静态 Web 资源层仅记录这种关系。 + +对于 Related 和 Alternative 资源,其 `RelatedAsset` 属性指向其所依赖的主资源。这种依赖链可多层嵌套,以表示一个资源的多种表示形式。静态 Web 资源仅记录这些信息,具体如何使用由 MSBuild 目标决定。 + +`AssetTraitName` 和 `AssetTraitValue` 用于区分相关/替代资源与其主资源。例如: + +- 对于全球化程序集,可记录程序集的文化(culture); +- 对于压缩资源,可记录编码方式(如 gzip、brotli)。 + +下图展示了在构建过程中被调用的MSBuild Target: + +![image-20251231225433184](./aspnetcore-swa/image-20251231225433184.webp) + +Sdk提供了一些重要的MSBuild Task供程序员调用: + +- `DefineStaticWebAssets`:该Task扫描提供了一系列候选的资产文件并构建一个*标准化的*静态资产对象; +- `DefineStaticWebAssetEndpoints`:该Task以上一个任务输出的静态资产对象为输入,输出每个静态资产文件的Web终结点; + +在构建中过程中`GenerateStaticWebAssetsManifest`和`GenerateStaticWebAssetsDevelopmentManifest`、`GenerateStaticWebAssetEndpointsManifest`等几个任务会产生一个重要的清单文件,这些文件通常存放在*obj*文件夹中,名称为`staticwebassets.*.json`。其中一个较为重要的清单文件是`staticwebassets.development.json`,其存储了所有的静态资产文件和对应的存储目录。这个文件在构建的过程中会被复制到输出目录`bin`中,名称为`$(PackageId).staticwebassets.runtime.json`。这个文件将会在生产模式下被静态资产中间件读取,作为建立静态文件终结点到实际物理文件的索引。这个文件也为需要调试`StaticWebAssetsSdk`的程序员提供了重要的调试信息,是解决ASP.NET Core中静态资产问题的不二法门。 + +### 解决方案 + +现在已经充分了解了`StaticWebAssetsSdk`,可以来设计在MSBuild中集成前端工具并生成最终静态资产文件的管线了。 + +首先来研究过程的步骤,`npm`或者其类似物也使用类似于MSBuild的先还原再构建两步,首先需要安装程序中使用到的包,然后在运行构建指令构建对应的静态文件,构建完成之后还需要将构建产物交给MSBuild中的静态资产处理管线进行进一步的处理。因此设计如下的三个步骤: + +1. `RestoreClientAssets`,这个Target需要运行`npm install`或者类似的指令安装依赖包; +2. `BuildClientAssets`,这个Target运行`npm run build`或者类似的指令构建项目; +3. `DefineClientAssets`,这个Target调用`DefineStaticWebAssets`等Task声明静态资产文件。 + +确定好生成步骤之后,声明一下会在生成过程中会用到的,可以提供给用户自定义的属性。安装和构建的相应软件包肯定是需要提供给用户自定义的。在一般情况下,前端工具链把将静态文件生成到`dist`文件夹中。为了符合MSBuild的惯例,这里将中间静态文件生成到*obj*文件夹下的`ClientAssets`文件夹中。为了实现这一点,构建过程中的指令就需要支持一个指定生成目录的参数,这个参数也作为一个属性暴露给用户可以自定义。这里就形成了下面三个提供给用户自定义的参数。 + +```xml + + pnpm install + pnpm run build + --output + +``` + +最终就是完成的构建原始代码了。第一个运行的构建目标`RestoreClientAssets`将会在`DispatchToInnerBuilds`任务运行之后运行,这个目标是MSBuild构建管线中一个不论是针对单架构生成还是多架构生成都只会运行一次的目标,这样在项目需要同时编译到.NET 8和.NET 10的情况下,仍然只会运行前端的安装命令一次。`BuildClientAssets`目标紧接着`RestoreClientAssets`目标的运行而运行,并将所有生成的前端文件添加到`_ClientAssetsBuildOutput`项中。最终的`DefineClientAssets`目标在负责解析项目中的所有静态文件的目标`ResolveWebAssetsConfiguration`运行之前运行,调用`DefineStaticWebAssets`和`DefineStaticWebAssetEndpoints`将前面生成的所有前端静态文件添加到MSBuild的静态文件处理管线中。 + +```xml + + <_RestoreClientAssetsBeforeTargets Condition="'$(TargetFramework)' == ''">DispatchToInnerBuilds + + + + + + + + + + <_ClientAssetsOutputFullPath>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)ClientAssets')) + + + + + + + <_ClientAssetsBuildOutput Include="$(IntermediateOutputPath)ClientAssets\**"/> + + + + + + + + + + + + + + + + + +``` + +为了测试如下的代码,可以在项目中新建一个`Directory.Build.targets`文件,将上述的内容复制进去进行测试,当然别忘了用``标签包裹这一切。 diff --git a/source/posts/aspnetcore-swa/image-20251231225433184.webp b/source/posts/aspnetcore-swa/image-20251231225433184.webp new file mode 100644 index 0000000..003101d --- /dev/null +++ b/source/posts/aspnetcore-swa/image-20251231225433184.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e50362d4eaebe435faba8a889442930cc069c9218ea8667ec8941b7f2ac9afc7 +size 81062 diff --git a/YaeBlog/source/posts/big-homework.md b/source/posts/big-homework.md similarity index 98% rename from YaeBlog/source/posts/big-homework.md rename to source/posts/big-homework.md index e699423..6f8b801 100644 --- a/YaeBlog/source/posts/big-homework.md +++ b/source/posts/big-homework.md @@ -1,11 +1,11 @@ --- title: 人生代码大作业初体验 +date: 2022-07-27T11:34:49.0000000 tags: - - 随笔 -typora-root-url: big-homework -date: 2022-07-27 11:34:49 +- 杂谈 --- + 在大学也呆了一年了,终于遇上了第一个需要多人合作的写代码项目。从四月底分组完成,任务部署下来到七月初接近尾声,在这两个多月的时间里,也算是经历了不少,学到了不少。 @@ -44,7 +44,7 @@ date: 2022-07-27 11:34:49 而且采用 `Git`还有一个好处,采用 `Github`的 `Insight`功能可以轻松的看出大家的贡献值()。 -![img](1.png) +![img](1.webp) ## 一些技术上的收获 diff --git a/source/posts/big-homework/1.webp b/source/posts/big-homework/1.webp new file mode 100644 index 0000000..21ce881 --- /dev/null +++ b/source/posts/big-homework/1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7b9d0cee70e8310ba9a7e0c771bea6e8eb62714fa37c4cce98bb9c7db7fc9f5 +size 19390 diff --git a/YaeBlog/source/posts/build-blog-record.md b/source/posts/build-blog-record.md similarity index 99% rename from YaeBlog/source/posts/build-blog-record.md rename to source/posts/build-blog-record.md index 248b787..ec9ba27 100644 --- a/YaeBlog/source/posts/build-blog-record.md +++ b/source/posts/build-blog-record.md @@ -1,12 +1,12 @@ --- title: 建立博客过程的记录 -typora-root-url: 建立博客过程的记录 -date: 2022-04-08 11:52:32 +date: 2022-04-08T11:52:32.0000000 tags: - - 技术笔记 +- 技术笔记 --- + 当我已经在Python的浩瀚大海遨(zheng)游(zha)了半个暑假后,我决定尝试一下传说中程序员专用的学(zhuang)习(bi)手(fangfa)段(fa)——建立自己的个人博客。作为一个半懂不懂的Python程序员,心中冒出的第一个想法自然是采用Python的Django作为开发自己的个人博客的手段。然而,在阅读了[用Django搭建个人博客](https://www.dusaiphoto.com/article/2/)等的其他人搭建这类动态博客的过程记录之后,我便义无反顾的转向了采用javascript开发的博客框架[Hexo](https://hexo.io),说好的Python信仰呢。无他,唯简单尔。 @@ -131,7 +131,7 @@ Hexo init blog ``` Hexo会以blog为名称创建一个博客文件夹,这个文件夹的内容为 -![文件夹截图](1.png) +![文件夹截图](1.webp) `node_modules`文件夹是Hexo需要用到的一些npm依赖包的存放地址,`public`文件夹下是由Hexo渲染产生的静态博客文件,`scaffolds`文件夹是博客用到的模板文件,在默认情况下应该有`draft.md`,`page.md`,`post.md`三个模板文件。`themes`是Hexo中可以使用的主题文件。主题也是Hexo一个非常方便的设计,我们可以方便使用其他人编写的Hexo Themes,让自己的博客在不同的风格之间变换。`source`文件夹就是存放我们写作的博客的地方。一般这里面会有两个子文件夹,`_draft`, `_posts`。我们在里面在创建一个`img`文件夹,把自己的头像图片和网站的图标文件都放在里面,在之后的设置的时候使用。 @@ -146,7 +146,7 @@ INFO Hexo is running at http://localhost:4000/ . Press Ctrl+C to stop. 会在本地运行Hexo自带的一台静态博客服务器。我们用浏览器访问http://localhost:4000, 就可以看见Hexo博客的初始界面 -![初始截图](2.png) +![初始截图](2.webp) 这便说明安装成功了,~~可以开香槟了~~ diff --git a/source/posts/build-blog-record/1.webp b/source/posts/build-blog-record/1.webp new file mode 100644 index 0000000..b72db8a --- /dev/null +++ b/source/posts/build-blog-record/1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a11c506a9360640034aa30fd9d6204d273f0fe7b707516a5269d28b79bacff81 +size 9358 diff --git a/source/posts/build-blog-record/2.webp b/source/posts/build-blog-record/2.webp new file mode 100644 index 0000000..d5c9269 --- /dev/null +++ b/source/posts/build-blog-record/2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dbd874b7763d86ccf67228ba263cc57d048241497ad91ed4bac50fd63f7ed34 +size 39286 diff --git a/YaeBlog/source/posts/build-dotnet-from-source.md b/source/posts/build-dotnet-from-source.md similarity index 98% rename from YaeBlog/source/posts/build-dotnet-from-source.md rename to source/posts/build-dotnet-from-source.md index 200a665..87fd851 100644 --- a/YaeBlog/source/posts/build-dotnet-from-source.md +++ b/source/posts/build-dotnet-from-source.md @@ -7,6 +7,7 @@ tags: --- + 我们编译是这样的,在本平台上编译只要敲三条命令就好了,而交叉编译要考虑的就很多了。 @@ -45,7 +46,7 @@ tags: 通常一份GNU工具链只能针对一个平台进行编译,但是LLVM工具链是一套先天的交叉编译工具链,例如对于`llc`工具,使用`llc --version`命令可以看见该编译器可以生成多种目标平台上的汇编代码: -![image-20240824120646587](./build-dotnet-from-source/image-20240824120646587.png) +![image-20240824120646587](./build-dotnet-from-source/image-20240824120646587.webp) 在使用`clang++`时加上`--target=`指定目标三元组就可以进行交叉编译。 @@ -62,7 +63,7 @@ int main() } ``` -![image-20240824121425007](./build-dotnet-from-source/image-20240824121425007.png) +![image-20240824121425007](./build-dotnet-from-source/image-20240824121425007.webp) 看样子交叉编译也不是开箱即用的。最开始我们猜想系统提供的LLVM工具链没有被配置为交叉编译,因此尝试在本地自行编译一套LLVM工具链。 @@ -81,7 +82,7 @@ cmake ../llvm-project.src/llvm \ 编译之后的成果会安装到`/usr/local/`目录下,而在`$PATH`环境变量中`/usr/local`位置将在`/usr`目录之前,因此调用时将会优先调用我们自行编译的LLVM工具链,而不是系统中安装的LLVM工具链。 -![image-20240824134158262](./build-dotnet-from-source/image-20240824134158262.png) +![image-20240824134158262](./build-dotnet-from-source/image-20240824134158262.webp) 但是使用这套编译工具链仍然会爆出和之前一样的问题。说明这并不是系统安装LLVM工具链的问题。仔细一想也确实,这里提示找不到对应的头文件应该是找不到RISC-V架构之下的头文件——这里的也是交叉编译的主要问题所在:虽然LLVM工具链宣称自己是原生支持交叉编译的,但是没人宣称说标准库和头文件是原生的。这里我们就需要一个根文件系统来提供这些头文件和各种库文件。 @@ -198,7 +199,7 @@ clang++ --target=riscv64-linux-gnu --sysroot=$ROOTFS_DIR -fuse-ld=lld hello.cpp 第一个问题的回答是Arch Linux安装的LLVM工具是可以交叉编译的。虽然在Arch Linux官方构建LLVM工具链的[构建脚本](https://gitlab.archlinux.org/archlinux/packaging/packages/clang/-/blob/main/PKGBUILD?ref_type=heads)中没有使用`LLVM_TARGETS_TO_BUILD`参数,但是这个参数的默认值是`all`。这一点我们也可以通过实验来验证。 -![image-20240824153514149](./build-dotnet-from-source/image-20240824153514149.png)于是回到编译`llvm`的目录下执行`cat install_manifest.txt | sudo xargs rm`。 +![image-20240824153514149](./build-dotnet-from-source/image-20240824153514149.webp)于是回到编译`llvm`的目录下执行`cat install_manifest.txt | sudo xargs rm`。 第二个问题的回答可以使用实验来验证,首先安装`riscv64-linux-gnu-gcc`,然后将根文件系统的位置设置为`/usr/riscv64-linux-gnu`,重新编译上面的你好世界样例。编译之后可以正常执行。 @@ -229,4 +230,4 @@ export ROOTFS_DIR= 但是现在的.NET在RISC-V平台上还是废物一个,甚至连`dotnet new`都跑不过,下一步看看能不能运行一下运行时的测试集看看。 -![image-20240824214145759](./build-dotnet-from-source/image-20240824214145759.png) +![image-20240824214145759](./build-dotnet-from-source/image-20240824214145759.webp) diff --git a/source/posts/build-dotnet-from-source/image-20240824120646587.webp b/source/posts/build-dotnet-from-source/image-20240824120646587.webp new file mode 100644 index 0000000..053a39b --- /dev/null +++ b/source/posts/build-dotnet-from-source/image-20240824120646587.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a1255246ecef4fd56e7e8053bf83cab43c8392b2baa6a7519742dc880d6ba73 +size 80142 diff --git a/source/posts/build-dotnet-from-source/image-20240824121425007.webp b/source/posts/build-dotnet-from-source/image-20240824121425007.webp new file mode 100644 index 0000000..d86de6a --- /dev/null +++ b/source/posts/build-dotnet-from-source/image-20240824121425007.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4770f52360bdf2c08ca7cdaf6a27ec2e5b7ea43da160666ad85e3a15b63a629b +size 17044 diff --git a/source/posts/build-dotnet-from-source/image-20240824134158262.webp b/source/posts/build-dotnet-from-source/image-20240824134158262.webp new file mode 100644 index 0000000..e558fa7 --- /dev/null +++ b/source/posts/build-dotnet-from-source/image-20240824134158262.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a96043307024619a028381259714d9fbaa3c86e858d6085c9c525de492ac4b3f +size 73898 diff --git a/source/posts/build-dotnet-from-source/image-20240824153514149.webp b/source/posts/build-dotnet-from-source/image-20240824153514149.webp new file mode 100644 index 0000000..a987a1c --- /dev/null +++ b/source/posts/build-dotnet-from-source/image-20240824153514149.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bcec03c67fb5f8552a6884eed38ad4d709ea0d0be60b945030744ed809b7d6b +size 18058 diff --git a/source/posts/build-dotnet-from-source/image-20240824214145759.webp b/source/posts/build-dotnet-from-source/image-20240824214145759.webp new file mode 100644 index 0000000..5b6bdbc --- /dev/null +++ b/source/posts/build-dotnet-from-source/image-20240824214145759.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bf8710c2b558f1d33d6adbf294c31f82773453fcb2bcb6d7f062b8ca90c2bd1 +size 33928 diff --git a/YaeBlog/source/posts/c-include-problems.md b/source/posts/c-include-problems.md similarity index 96% rename from YaeBlog/source/posts/c-include-problems.md rename to source/posts/c-include-problems.md index 5993d12..6839ac3 100644 --- a/YaeBlog/source/posts/c-include-problems.md +++ b/source/posts/c-include-problems.md @@ -1,12 +1,12 @@ --- title: C项目中有关头文件的一些问题 +date: 2022-05-08T11:35:19.0000000 tags: - - 技术笔记 - - C/C++ -typora-root-url: c-include-problems -date: 2022-05-08 11:35:19 +- 技术笔记 +- C/C++ --- + 最近在完成一门`C`语言课程的大作业,课设老师要求我们将程序分模块的开发。在编写项目头文件的时候,遇到了一些令本菜鸡大开眼界的问题。 @@ -17,7 +17,7 @@ date: 2022-05-08 11:35:19 我项目的结构大致如图所示: -![](1.png) +![](1.webp) 在`include`的头文件目录下有两个头文件,`rail.h`和`bus.h`,这两个头文件分别定义了两个结构体`rail_node_t`和`bus_t`。 @@ -68,7 +68,7 @@ typedef struct bus bus_t; 项目的`test`文件夹下是单元测试文件夹,但是在编译的时候会报错 -![](2.png) +![](2.webp) 大意就是在一个google test内部的头文件中有几个函数找不到定义,这个函数都位于`io.h`这个头文件中。 diff --git a/source/posts/c-include-problems/1.webp b/source/posts/c-include-problems/1.webp new file mode 100644 index 0000000..59798b8 --- /dev/null +++ b/source/posts/c-include-problems/1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eebdf09480fdcf51b5d110c0f1a78649df4bae999dd8151c2a4bf49333ef7e4a +size 4450 diff --git a/source/posts/c-include-problems/2.webp b/source/posts/c-include-problems/2.webp new file mode 100644 index 0000000..c40a685 --- /dev/null +++ b/source/posts/c-include-problems/2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:779960f8c829262183e2fdce6888b9f25dce625b60862d925d79195830a4e7b4 +size 210008 diff --git a/YaeBlog/source/posts/cncc-2024.md b/source/posts/cncc-2024.md similarity index 97% rename from YaeBlog/source/posts/cncc-2024.md rename to source/posts/cncc-2024.md index 42d79d9..3a0e19f 100644 --- a/YaeBlog/source/posts/cncc-2024.md +++ b/source/posts/cncc-2024.md @@ -5,13 +5,14 @@ tags: - 杂谈 --- + 2024年的中国计算机大会于10月24日到10月26日在浙江省金华市东阳市横店镇举办,而鄙人在下不才我,有幸受到实验室资助前去参观学习。 首先开幕式镇楼。 -![image-20241102212738598](./cncc-2024/image-20241102212738598.png) +![image-20241102212738598](./cncc-2024/image-20241102212738598.webp) ## 学术上 @@ -21,11 +22,11 @@ tags: 第一个报告是华为庞加莱实验室秦彬娟老师的《异构智算时代的操作系统演进》。报告高屋建瓴,从比较宏观的角度上介绍了当前异构融合操作系统诞生的背景、发展的方向。在报告中重点介绍了一种异构融合操作系统的设计思路:通过三层架构,基于互联池化技术,构建AI时代的融合算力系统。系统中的三层包括:(1)池化基础底层,包括多设备的融合和池化设备虚拟化;(2)异构融合核心子系统,例如异构融合调度系统、异构融合内存和异构融合存储系统;(3)异构核心服务。总的来说,这个报告在一定程度上勾勒出了未来一个异构融合操作系统应有的各项功能,但是显然这一操作系统的实现还存在着明显的困难。 -![image-20241102211959206](./cncc-2024/image-20241102211959206.png) +![image-20241102211959206](./cncc-2024/image-20241102211959206.webp) 下面一个报告是较为有干货的报告,北京航空航天大学刘瀚骋老师的《异构融合OS及多样性内存管理框架》。报告中介绍了一个称作`FMMU`的系统,是对于异构融合操作系统中内存管理系统的探索。报告中首先介绍了内存池化技术对于异构融合操作系统的重要性,指出分布式共享内存(Distributed Shared Memory)可能是实现内存池化技术的未来。然后介绍了将部分内存管理中的计算卸载到可编程网络硬件中来加速分布式内存访问的新思路。最后在报告中提到了内存管理技术如何解决错误预测和错误回复的问题。虽然在听的时候没太注意,但是现在总结的时候才发现这个报告的思路似乎有点混乱,尤其是最后一点和内存管理系统并没有什么直接的关系,而且这个内存管理系统似乎不是**异构系统**的内存管理,反而是分布式系统的内存管理。不过总的来说,这个报告还是非常实际的,介绍了不少当前异构融合操作系统中的内存管理面临的问题和解决问题的探索。 -![image-20241102212355390](./cncc-2024/image-20241102212355390.png) +![image-20241102212355390](./cncc-2024/image-20241102212355390.webp) 第三个报告是国防科技大学李东升老师的《异构计算环境下的分布式深度学习训练》。报告首先从李老师的主业——并行计算起手,介绍了深度学习训练过程中主要的各种并行方法,例如数据并行、模型并行和混合并行等,指出目前大模型的并行训练存在着计算/存储/通信难的问题。因此,提出了一个智能模型训练并行任务划分方法:(1)基于符号算子的计算图定义方法;(2)面向Transformer模型的流水线并行任务划分方法;(3)异构资源感知的流水线并行任务划分方法。然后针对分布式模型训练中通信调度存在的通信墙、数据依赖关系复杂等的问题,提出综合词嵌入表的稀疏通信调度技术、流水线并行的P2P通信调度技术、模型计算的统一操作执行引擎和网络链路感知的通信执行引擎的通信调度技术。最后提到了智能模型训练 的内存优化技术,针对现有重计算技术(re-computing)和存储交换(swapping)技术存在的问题,提出了一种面向大型智能模型训练的细粒度内存优化方法`DELTA`。 @@ -49,7 +50,7 @@ Plane讨论没有参加。 第二个报告是南京大学冯新宇老师的《基于仓颉语言的嵌入式DSL开发》,同时冯新宇老师也是仓颉语言的首席架构师。冯老师的这个报告主要聚焦于仓颉语言提供的嵌入式DSL能力,而嵌入式DSL这一设计范式已经在前端开发中展现了不俗的潜力。报告中介绍了嵌入式DSL出现的背景,仓颉中为了提供嵌入式DSL而引入的语法糖、仓颉提供的嵌入式DSL工具箱等。虽然仓颉语言是一个主要面向上层应用开发的语言,但是仓颉中丰富的DSL能力还是给异构编程模型的设计提供了不少的启发。而且目前在各种深度学习编译器中DSL的应用也非常广泛,例如`triton`。 -![image-20241102212536635](./cncc-2024/image-20241102212536635.png) +![image-20241102212536635](./cncc-2024/image-20241102212536635.webp) 第三个报告是在存算一体的芯片上做数据库的加速,第四个报告是OpenHarmony上`ArkTS`程序的静态分析,都没怎么听。 diff --git a/source/posts/cncc-2024/image-20241102211959206.webp b/source/posts/cncc-2024/image-20241102211959206.webp new file mode 100644 index 0000000..d0fa3bb --- /dev/null +++ b/source/posts/cncc-2024/image-20241102211959206.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c57e583d8087b8f42c15cc8b351d8053af6f7a5a00179b846bcdfae9f68fc3a0 +size 139100 diff --git a/source/posts/cncc-2024/image-20241102212355390.webp b/source/posts/cncc-2024/image-20241102212355390.webp new file mode 100644 index 0000000..08402fc --- /dev/null +++ b/source/posts/cncc-2024/image-20241102212355390.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4635d95dc1024cba26c8fee048df2efeb531ab2480d9179456c1919ec2d62c9a +size 243730 diff --git a/source/posts/cncc-2024/image-20241102212536635.webp b/source/posts/cncc-2024/image-20241102212536635.webp new file mode 100644 index 0000000..f050150 --- /dev/null +++ b/source/posts/cncc-2024/image-20241102212536635.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df0984d43f790ca38a0c42560b450fc68cd21ac5827b4238882798c21425ea31 +size 48104 diff --git a/source/posts/cncc-2024/image-20241102212738598.webp b/source/posts/cncc-2024/image-20241102212738598.webp new file mode 100644 index 0000000..b2ca998 --- /dev/null +++ b/source/posts/cncc-2024/image-20241102212738598.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82131843f7a9c6c402015d3d7f81dc63f6671a9189d02bd60f0f668ec21b74ca +size 654436 diff --git a/YaeBlog/source/posts/compile-mediapipe.md b/source/posts/compile-mediapipe.md similarity index 99% rename from YaeBlog/source/posts/compile-mediapipe.md rename to source/posts/compile-mediapipe.md index 56efebb..e1bb692 100644 --- a/YaeBlog/source/posts/compile-mediapipe.md +++ b/source/posts/compile-mediapipe.md @@ -1,11 +1,12 @@ --- title: 编译MediaPipe框架 +date: 2022-11-11T22:20:25.0000000 tags: - - C/C++ - - 技术笔记 -date: 2022-11-11 22:20:25 +- C/C++ +- 技术笔记 --- + 编译MediaPipe框架。 @@ -198,7 +199,7 @@ bazel build -c opt --strip=ALWAYS \ 如果在编译的过程中提示缺失`dx.jar`这个文件而且你用的SDK版本还是高于31的,那可能是SDK中缺失了这个文件,可以将SDk降级到30就含有这个文件了。我使用的解决办法比较离奇,我是将30版本的SDK文件中的这个文件软链接过来,解决了这个问题。 -![](compile-mediapipe/2023-01-15-22-05-41-Screenshot_20230115_220521.png) +![](compile-mediapipe/2023-01-15-22-05-41-Screenshot_20230115_220521.webp) 编译消耗的时间可能比较的长,耐心等待即可。 @@ -227,7 +228,7 @@ bazel build -c opt //mediapipe/graphs/pose_tracking:pose_tracking_gpu_binary_gra 然后还需要从服务器上下载`tflite`文件,`Pose Tracking`这个解决方案需要两个`tflite`文件,第一个是[pose_detection.tflite](https://storage.googleapis.com/mediapipe-assets/pose_detection.tflite),第二个文件则有三个不同的选择,分别对于解决方案中提供的三个质量版本: -![](compile-mediapipe/2023-01-19-20-20-40-Screenshot_20230119_202008.png) +![](compile-mediapipe/2023-01-19-20-20-40-Screenshot_20230119_202008.webp) 下载地址是[pose_landmark_full.tflite](https://storage.googleapis.com/mediapipe-assets/pose_landmark_full.tflite),[pose_landmark_heavy.tflite](https://storage.googleapis.com/mediapipe-assets/pose_landmark_heavy.tflite)和[pose_landmark_lite.tflite](https://storage.googleapis.com/mediapipe-assets/pose_landmark_lite.tflite)。 diff --git a/source/posts/compile-mediapipe/2023-01-15-22-05-41-Screenshot_20230115_220521.webp b/source/posts/compile-mediapipe/2023-01-15-22-05-41-Screenshot_20230115_220521.webp new file mode 100644 index 0000000..aaf12b3 --- /dev/null +++ b/source/posts/compile-mediapipe/2023-01-15-22-05-41-Screenshot_20230115_220521.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5a965740f47cf499c950b32ce8f0b9acdb6a6f47210e0f31a9f723173e57b6b +size 50502 diff --git a/source/posts/compile-mediapipe/2023-01-19-20-20-40-Screenshot_20230119_202008.webp b/source/posts/compile-mediapipe/2023-01-19-20-20-40-Screenshot_20230119_202008.webp new file mode 100644 index 0000000..5429353 --- /dev/null +++ b/source/posts/compile-mediapipe/2023-01-19-20-20-40-Screenshot_20230119_202008.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:819ba78fcbd2115d7f85b54c2eab6f4bf7ed3990c8e5546163c8769b03321ac0 +size 7640 diff --git a/YaeBlog/source/posts/computer-architecture-pipeline.md b/source/posts/computer-architecture-pipeline.md similarity index 94% rename from YaeBlog/source/posts/computer-architecture-pipeline.md rename to source/posts/computer-architecture-pipeline.md index 6228525..06176f5 100644 --- a/YaeBlog/source/posts/computer-architecture-pipeline.md +++ b/source/posts/computer-architecture-pipeline.md @@ -1,20 +1,21 @@ --- title: 计算机系统结构——流水线复习 +date: 2024-06-12T20:27:25.0000000 tags: - - 计算机系统结构 - - 学习资料 -date: 2024-06-12 20:27:25 +- 计算机系统结构 +- 学习资料 --- + 让指令的各个执行阶段依次进行运行是一个简单而自然的想法,但是这种方式执行速度慢、运行效率低。因此一个很自然的想法就是将指令重叠起来运行,让执行功能部件被充分的利用起来,这就是**流水线**。 流水线的表示方法有两种。 -![image-20240612184855300](computer-architecture-pipeline/image-20240612184855300.png) +![image-20240612184855300](computer-architecture-pipeline/image-20240612184855300.webp) 第一种被称作**连接图**,清晰的表达出了流水线内部的逻辑关系。 -![image-20240612184949777](computer-architecture-pipeline/image-20240612184949777.png) +![image-20240612184949777](computer-architecture-pipeline/image-20240612184949777.webp) > 上图中给出了两个流水线中的概念:通过时间和排空时间。其中通过时间又被称作装入时间,是指第一个任务进入流水线到完成的事件;排空时间则相反,是最后一个任务通过流水线的时间。 @@ -40,7 +41,7 @@ date: 2024-06-12 20:27:25 - 静态流水线,同一时间内,多功能流水线的各段只能按照同一种功能的方式连接。 - 动态流水线,同一时间内,多功能流水线的各种可以按照不同的方式连接,执行不同的功能。 -![image-20240612190426368](computer-architecture-pipeline/image-20240612190426368.png) +![image-20240612190426368](computer-architecture-pipeline/image-20240612190426368.webp) 按照流水线中是否存在反馈回路分类: @@ -58,7 +59,7 @@ date: 2024-06-12 20:27:25 - 加速比,同一任务,不使用流水线所使用时间与使用流水线所用时间比。 - 效率,流水线设备的利用率。 -![image-20240612192700169](computer-architecture-pipeline/image-20240612192700169.png) +![image-20240612192700169](computer-architecture-pipeline/image-20240612192700169.webp) 在设计流水线的过程中存在若干问题。 @@ -68,7 +69,7 @@ date: 2024-06-12 20:27:25 一个典型的五段流水线MIPS流水线: -![image-20240612193301372](computer-architecture-pipeline/image-20240612193301372.png) +![image-20240612193301372](computer-architecture-pipeline/image-20240612193301372.webp) diff --git a/source/posts/computer-architecture-pipeline/image-20240612184855300.webp b/source/posts/computer-architecture-pipeline/image-20240612184855300.webp new file mode 100644 index 0000000..4e7975e --- /dev/null +++ b/source/posts/computer-architecture-pipeline/image-20240612184855300.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10f512f3ccd787ac8168ad92d37fdc388dd2d0a913e5f8f0a660be6f6d7fe3d0 +size 9974 diff --git a/source/posts/computer-architecture-pipeline/image-20240612184949777.webp b/source/posts/computer-architecture-pipeline/image-20240612184949777.webp new file mode 100644 index 0000000..b3fc8fb --- /dev/null +++ b/source/posts/computer-architecture-pipeline/image-20240612184949777.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5099ed84c349c7694e281f1443f377cb3ab2006b8dca93fd4a9b9fbacfaf554f +size 35682 diff --git a/source/posts/computer-architecture-pipeline/image-20240612190426368.webp b/source/posts/computer-architecture-pipeline/image-20240612190426368.webp new file mode 100644 index 0000000..5c171ad --- /dev/null +++ b/source/posts/computer-architecture-pipeline/image-20240612190426368.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b3ecec450c6c7d39fc262f2cca842f776b00a5a17fd90592fbf5d5bac9ef95b +size 102504 diff --git a/source/posts/computer-architecture-pipeline/image-20240612192700169.webp b/source/posts/computer-architecture-pipeline/image-20240612192700169.webp new file mode 100644 index 0000000..abf867a --- /dev/null +++ b/source/posts/computer-architecture-pipeline/image-20240612192700169.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:291b80deb01bef18bebbf3e7774bd0ea9b65f8a6ba54b6d0609c7e98a45a1f45 +size 21358 diff --git a/source/posts/computer-architecture-pipeline/image-20240612193301372.webp b/source/posts/computer-architecture-pipeline/image-20240612193301372.webp new file mode 100644 index 0000000..a276da0 --- /dev/null +++ b/source/posts/computer-architecture-pipeline/image-20240612193301372.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c57f6d1869747aed4f08714f14e3c4c5ac88c3d601e2f7e90d2c75fc7dd998b +size 86268 diff --git a/YaeBlog/source/posts/daily-linux-0.md b/source/posts/daily-linux-0.md similarity index 97% rename from YaeBlog/source/posts/daily-linux-0.md rename to source/posts/daily-linux-0.md index 413c1f3..1b50da4 100644 --- a/YaeBlog/source/posts/daily-linux-0.md +++ b/source/posts/daily-linux-0.md @@ -1,12 +1,12 @@ --- title: 日用Linux挑战 第0篇 初见Arch Linux +date: 2023-01-15T22:23:08.0000000 tags: - - Linux - - 随笔 -date: 2023-01-15 22:23:08 -typora-root-url: daily-linux-0 +- Linux +- 杂谈 --- + 在将开发重心移到`WSL`上一年之后,我最终还是决定完全抛弃Windows,转向使用Linux作为我日常使用的主力系统。目前,我已经使用Linux作为主力系统一个月了。 @@ -92,7 +92,7 @@ sudo systemctl enable sddm.service 我目前实现的效果大概长这样: -![](2023-01-12-13-28-38-Screenshot_20230112_132829.png) +![](2023-01-12-13-28-38-Screenshot_20230112_132829.webp) 颇有一种`Windows`和`MacOS`杂交的风格。 @@ -106,7 +106,7 @@ sudo systemctl enable sddm.service 先上一张`shell`的系统概览截图: -![](2023-01-12-13-36-45-Screenshot_20230112_133628.png) +![](2023-01-12-13-36-45-Screenshot_20230112_133628.webp) 终端模拟器直接使用的`konsole`,目前没有进行改动。 diff --git a/source/posts/daily-linux-0/2023-01-12-13-28-38-Screenshot_20230112_132829.webp b/source/posts/daily-linux-0/2023-01-12-13-28-38-Screenshot_20230112_132829.webp new file mode 100644 index 0000000..219d603 --- /dev/null +++ b/source/posts/daily-linux-0/2023-01-12-13-28-38-Screenshot_20230112_132829.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6482d2eddccfe45c38a1fa3aa22ab7b92cba0fae89a5f7f957b039b363802e54 +size 141138 diff --git a/source/posts/daily-linux-0/2023-01-12-13-36-45-Screenshot_20230112_133628.webp b/source/posts/daily-linux-0/2023-01-12-13-36-45-Screenshot_20230112_133628.webp new file mode 100644 index 0000000..e991e55 --- /dev/null +++ b/source/posts/daily-linux-0/2023-01-12-13-36-45-Screenshot_20230112_133628.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a073ba53b1db67218108de9094841424e42dd29fe17762ebcf49b01e6dc3b9e +size 75008 diff --git a/YaeBlog/source/posts/daily-linux-1.md b/source/posts/daily-linux-1.md similarity index 99% rename from YaeBlog/source/posts/daily-linux-1.md rename to source/posts/daily-linux-1.md index c280fc3..2910e5e 100644 --- a/YaeBlog/source/posts/daily-linux-1.md +++ b/source/posts/daily-linux-1.md @@ -2,7 +2,7 @@ title: 日用Linux挑战 第1篇 问题与挑战 tags: - Linux - - 随笔 + - 杂谈 date: 2023-03-08 22:37:29 --- diff --git a/YaeBlog/source/posts/daily-linux-2.md b/source/posts/daily-linux-2.md similarity index 97% rename from YaeBlog/source/posts/daily-linux-2.md rename to source/posts/daily-linux-2.md index 04ebae4..501276f 100644 --- a/YaeBlog/source/posts/daily-linux-2.md +++ b/source/posts/daily-linux-2.md @@ -1,13 +1,13 @@ --- title: 日用Linux挑战 第2篇 Wayland +date: 2023-07-23T11:44:34.0000000 tags: - - 随笔 - - Linux -date: 2023-07-23 11:44:34 -typora-root-url: daily-linux-2 +- 杂谈 +- Linux --- + 使用`Linux`6个月,我成功戒掉了原神。 @@ -18,7 +18,7 @@ typora-root-url: daily-linux-2 最近恰好被平铺式的窗口管理器种草,又在B站上看见一个动画绚丽的`wayland`合成器——[Hyprland](https://hyprland.org/),当即脑袋一热,就把`kde`干掉,装上了`hyprland`。 -![img](df4211f6be2724b3b4725f7ce5a4078818844857.jpg) +![img](df4211f6be2724b3b4725f7ce5a4078818844857.avif) 安装`hyprland`的过程非常舒适,`hyprland`被打包为一个单独的二进制文件,使用`pacman`安装之后直接在`tty`下执行: @@ -46,7 +46,7 @@ Hyprland 各种在学习过程中遇到的工具软件:基本上都工作运行良好。当然因为没有设置缩放的问题而导致字体都很小。因为如果在配置文件中设置缩放之后会导致字体发虚。下面的截图就是我将我的2K显示屏设置为150%缩放的效果,~~虽然在截图中的效果不明显~~。目前在常用软件中唯一让我十分不满意的软件是`wps`,使用体验完全无法和`offices`相提并论,目前我正在研究使用`wine`运行`offices`,如果成功了就再水一篇博客庆祝一下。 -![image-20230702205919301](image-20230702205919301.png) +![image-20230702205919301](image-20230702205919301.webp) > 最新的进展是使用`wine`没法安装学校提供的`office 2021`,同时我又不愿意使用古老的`office`版本,但是我发现一个称作`onlyoffice`的第三方软件蛮好用的,等我试用一段时间再说。 > diff --git a/YaeBlog/source/posts/daily-linux-2/df4211f6be2724b3b4725f7ce5a4078818844857.jpg b/source/posts/daily-linux-2/df4211f6be2724b3b4725f7ce5a4078818844857.avif similarity index 100% rename from YaeBlog/source/posts/daily-linux-2/df4211f6be2724b3b4725f7ce5a4078818844857.jpg rename to source/posts/daily-linux-2/df4211f6be2724b3b4725f7ce5a4078818844857.avif diff --git a/source/posts/daily-linux-2/image-20230702205919301.webp b/source/posts/daily-linux-2/image-20230702205919301.webp new file mode 100644 index 0000000..4426c73 --- /dev/null +++ b/source/posts/daily-linux-2/image-20230702205919301.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec9b887868bff77ddd78f214b9dbd57a043e0cfa72ff6d9016df6b98a0620d82 +size 54620 diff --git a/YaeBlog/source/posts/daily-linux-3.md b/source/posts/daily-linux-3.md similarity index 98% rename from YaeBlog/source/posts/daily-linux-3.md rename to source/posts/daily-linux-3.md index bf8bde1..3d8117c 100644 --- a/YaeBlog/source/posts/daily-linux-3.md +++ b/source/posts/daily-linux-3.md @@ -1,13 +1,13 @@ --- title: 日用Linux挑战 第3篇 放弃Wayland +date: 2023-09-04T14:47:46.0000000 tags: - - 随笔 - - Linux -typora-root-url: daily-linux-3 -date: 2023-09-04 14:47:46 +- 杂谈 +- Linux --- + 成也开源,败也开源。 @@ -53,7 +53,7 @@ date: 2023-09-04 14:47:46 - `Meta+F`全屏应用 - `Meta+W`关闭应用 -![](Screenshot_20230904_144149.png) +![](Screenshot_20230904_144149.webp) ### Fuck You NVIDIA diff --git a/source/posts/daily-linux-3/Screenshot_20230904_144149.webp b/source/posts/daily-linux-3/Screenshot_20230904_144149.webp new file mode 100644 index 0000000..eb11f2f --- /dev/null +++ b/source/posts/daily-linux-3/Screenshot_20230904_144149.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9c45c7a0eb055513f7bb2804793cac662033d6d3a49e0244b45df730145df0b +size 158686 diff --git a/YaeBlog/source/posts/daily-linux-4.md b/source/posts/daily-linux-4.md similarity index 98% rename from YaeBlog/source/posts/daily-linux-4.md rename to source/posts/daily-linux-4.md index 83d9c35..ec61f7d 100644 --- a/YaeBlog/source/posts/daily-linux-4.md +++ b/source/posts/daily-linux-4.md @@ -1,11 +1,12 @@ --- title: 日用Linux挑战 第4篇 新的开始 +date: 2024-03-09T14:00:00.0000000 tags: - - Linux - - 随笔 -date: 2024/03/09 14:00:00 +- Linux +- 杂谈 --- + 小步快跑,面向未来。 @@ -18,7 +19,7 @@ date: 2024/03/09 14:00:00 相较于古老但是稳定的`Ext4`文件系统,`Btrfs`对我来说最大的好处便是可以零成本的创建快照,便于在出现错误的时候及时回滚或者直接重装系统。因此,为了方便快照的生成和回滚,我在安装系统时使用**扁平化**的子分区划分方法:即尽力避免出现嵌套的子分区,所有需要快照的分区都处在`/`目录之下: -![Screenshot_20240309_115143](daily-linux-4/Screenshot_20240309_115143.png) +![Screenshot_20240309_115143](daily-linux-4/Screenshot_20240309_115143.webp) - `@`为根分区,挂载在`/`目录之下,打开写时复制; - `@home`为家目录分区,挂载在`/home`目录之下,打开写时复制; @@ -44,7 +45,7 @@ sudo btrfs send /.snapshots/home@20240225 | zstd | ssh root@remote "zstd -d | bt 算起来,我已经和`Wayland`显示协议相爱相杀了整整一年了,从`KDE plasma X`到`Hyprland`,再尝试小众的`labwc`,最后回到了`KDE plasma X`。而在2024年2月29日`KDE plasma`释出6.0版本,将`Wayland`作为默认的显示协议,我也在第一时间更新了版本并使用`wayland`显示协议。现在,我可以比较确定的说,`Wayland`目前已经达到可用的水平了,而且我还是使用`RTX 3060`显卡。 -![image-20240309130329784](daily-linux-4/image-20240309130329784.png) +![image-20240309130329784](daily-linux-4/image-20240309130329784.webp) 不过相较于`AMDGPU`可以开箱即用,使用`NVIDIA`启动需要配置如下的模块参数: @@ -64,7 +65,7 @@ options nvidia_drm modeset=1 fbdev=1 不过`XWayland`应用程序在使用`NVIDIA`驱动时会存在一个神奇的**同步失败**问题,表现为在`xwayland`中部分控件闪烁,交替显示更新前和更新后的帧,而且这个问题几乎不能被截屏抓到,具体可以见`freedesktop`上的这个[issue](https://gitlab.freedesktop.org/xorg/xserver/-/issues/1317)。虽然这个议题下面有着很长的讨论,还是建议大家完整的看一遍,里面甚至还有: -![image-20240309131750535](daily-linux-4/image-20240309131750535.png) +![image-20240309131750535](daily-linux-4/image-20240309131750535.webp) 省流:这个议题讨论了在`xserver`中提供显式同步的协议原语,方便图形驱动程序知道什么时候渲染的帧发生了变化。因此这并不是一个`NVIDIA`驱动程序的问题,而是需要将`Linux`显示协议栈从隐式同步迁移到显式同步。但是相关的工作还在开发过程中,因此解决方法有两个: @@ -77,9 +78,9 @@ options nvidia_drm modeset=1 fbdev=1 于是,我就在`Arch Wiki`上学到一条新知识: -![image-20240309134847166](daily-linux-4/image-20240309134847166.png) +![image-20240309134847166](daily-linux-4/image-20240309134847166.webp) 原来`efi`分区其实只用放`grub`,,, -![img](daily-linux-4/cfd17cff0701a8e8c69fecf247f17fc1-1709963611271-2.jpg) +![img](daily-linux-4/cfd17cff0701a8e8c69fecf247f17fc1-1709963611271-2.webp) diff --git a/source/posts/daily-linux-4/Screenshot_20240309_115143.webp b/source/posts/daily-linux-4/Screenshot_20240309_115143.webp new file mode 100644 index 0000000..b4250bd --- /dev/null +++ b/source/posts/daily-linux-4/Screenshot_20240309_115143.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61a07177866e25ab9b2b7b3d1eb260e1fbac17c9b1be428966d1a9fece5efe04 +size 13498 diff --git a/source/posts/daily-linux-4/cfd17cff0701a8e8c69fecf247f17fc1-1709963611271-2.webp b/source/posts/daily-linux-4/cfd17cff0701a8e8c69fecf247f17fc1-1709963611271-2.webp new file mode 100644 index 0000000..d55647c --- /dev/null +++ b/source/posts/daily-linux-4/cfd17cff0701a8e8c69fecf247f17fc1-1709963611271-2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4eae5bfda7882b8389e9a4dc1eae23468bb4d42270338913f0bd292245207f07 +size 201904 diff --git a/source/posts/daily-linux-4/image-20240309130329784.webp b/source/posts/daily-linux-4/image-20240309130329784.webp new file mode 100644 index 0000000..255d4de --- /dev/null +++ b/source/posts/daily-linux-4/image-20240309130329784.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b77fb0a670449e075dbedb15f48ca15a3af970ec78aa2044260b08cebb45af3 +size 106782 diff --git a/source/posts/daily-linux-4/image-20240309131750535.webp b/source/posts/daily-linux-4/image-20240309131750535.webp new file mode 100644 index 0000000..a88aef4 --- /dev/null +++ b/source/posts/daily-linux-4/image-20240309131750535.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66d2533db6f4f7ff214c3d8fa35da1a427609caa5cf72a1594d0ca3aab7db2f5 +size 61926 diff --git a/source/posts/daily-linux-4/image-20240309134847166.webp b/source/posts/daily-linux-4/image-20240309134847166.webp new file mode 100644 index 0000000..32b174d --- /dev/null +++ b/source/posts/daily-linux-4/image-20240309134847166.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d02f73b1307040111112191abc11959e0725c979dc0e0768233a8c9df63896b1 +size 20340 diff --git a/YaeBlog/source/posts/dotnet-performance-8.md b/source/posts/dotnet-performance-8.md similarity index 99% rename from YaeBlog/source/posts/dotnet-performance-8.md rename to source/posts/dotnet-performance-8.md index e2af79d..2657e90 100644 --- a/YaeBlog/source/posts/dotnet-performance-8.md +++ b/source/posts/dotnet-performance-8.md @@ -7,6 +7,7 @@ tags: - 编译原理 --- + JIT编译就一定比AOT编译慢吗? @@ -104,7 +105,7 @@ class Program 首先是为了更好发挥动态PGO的性能,JIT编译器中为分层编译引入了更多的编译层数。需要引入更多编译层数的原因主要有两点。第一,插入各种采样的指令和代码是需要代价的,考虑到第0层编译的主要目标是为了降低编译的时间,提高应用的启动速度,在第0层编译过程中就不能插入太多的采样指令。因此编译器首先增加了一个新的编译层——采样第0层来解决这个问题。大部分的方法将在第一次运行时编译到缺少优化、缺少采样指令的第0层,在运行时发现该方法被调用了多次之后,JIT编译器将这个方法重新编译到采样第0层,再经过一系列的调用之后,JIT编译器将利用采样得到的信息对该方法重新进行编译并优化。第二,在原始编译器模型中使用即时运行(R2R)方法编译的代码不能参加到动态PGO中,尤其是考虑到几乎所有应用程序都会调用的核心库代码是采用R2R的方式进行运行的,如果这部分的代码不能参加动态PGO将不能够完全发挥动态PGO的效果,虽然核心库在提前编译的过程中会使用静态PGO进行一部分的优化。因此JIT编译器为R2R编译好的代码增加了一个新的编译器,在运行时发现这部分代码被调用多次之后将会被JIT编译器编译到含有优化和采样代码的采样第1层,随着调用次数的增加这部分的代码将可以利用采样得到的信息进行优化。下面这张图展现了不同编译方法在运行过程中可能达到的编译层级。 -![image-20240828135354598](./dotnet-performance-8/image-20240828135354598.png) +![image-20240828135354598](./dotnet-performance-8/image-20240828135354598.webp) JIT编译器也在第0层编译的过程中引入了更多的优化。虽然第0层编译的目的是缩短编译的时间,但是许多的优化可以通过减少需要生成的代码数量来达到这个目的。常量折叠(Constant Folding)就是一个很好的例子。虽然这会让JIT编译器在第0层编译时花费更多的时间同运行时中的虚拟机交互来解析各种变量的类型,但是这可以大量的减少JIT编译器需要生成的代码量,尤其是对于下面这种涉及到类型判断的例子。 @@ -257,7 +258,7 @@ for (int trial = 0; trial < 5; trial++) 程序的输出是5次次采样统计的结果: -![image-20240828155556375](./dotnet-performance-8/image-20240828155556375.png) +![image-20240828155556375](./dotnet-performance-8/image-20240828155556375.webp) 需要指出的是,虽然在上面的代码中使用和运行时代码中一样的“蓄水池”大小,但是在运行时并没有提前获得所有需要统计的数据,调用的统计数据是由多个不同的运行线程同时写入蓄水池中的。从结果中可以看出,虽然数值上并不准确,但是该算法准确的统计出了各个字符的出现趋势。 @@ -532,7 +533,7 @@ public class Tests 为了优化这种生成周期和程序一致对象的内存管理,.NET 8中引入了一个新的堆——没有内存管理的堆。JIT编译器将会保证这些常量类型的对象将会被分配在这个堆中,这种没有GC管理的堆也意味着JIT编译器可以为这些对象使用一个固定的内存地址,在使用时避免掉了一次内存读取。 -![Heaps where .NET Objects Live](./dotnet-performance-8/HeapsWhereNetObjectsLive.png) +![Heaps where .NET Objects Live](./dotnet-performance-8/HeapsWhereNetObjectsLive.webp) 将上述提高的示例代码使用.NET 8版本进行编译得到的代码如下,从中也可以看出JIT编译器生成的代码只有一条`mov`指令,避免了一次内存访问。 diff --git a/source/posts/dotnet-performance-8/HeapsWhereNetObjectsLive.webp b/source/posts/dotnet-performance-8/HeapsWhereNetObjectsLive.webp new file mode 100644 index 0000000..87f2d5e --- /dev/null +++ b/source/posts/dotnet-performance-8/HeapsWhereNetObjectsLive.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c06db81eb612adf2c9110087a1f4ff121ac5fdb1bfaabb1aad22615e63a8505 +size 19238 diff --git a/source/posts/dotnet-performance-8/image-20240828135354598.webp b/source/posts/dotnet-performance-8/image-20240828135354598.webp new file mode 100644 index 0000000..24c61ad --- /dev/null +++ b/source/posts/dotnet-performance-8/image-20240828135354598.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e250fddca641dd8afcd3065cf90d74bb2f69d5ea15797dfdbde12b2cd7b506a +size 17798 diff --git a/source/posts/dotnet-performance-8/image-20240828155556375.webp b/source/posts/dotnet-performance-8/image-20240828155556375.webp new file mode 100644 index 0000000..ba912fe --- /dev/null +++ b/source/posts/dotnet-performance-8/image-20240828155556375.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d39a580333d9f7d214f9cc9c38d4b2b70bbec4267edfddb342f0e711c409888d +size 9904 diff --git a/YaeBlog/source/posts/environment-setting.md b/source/posts/environment-setting.md similarity index 98% rename from YaeBlog/source/posts/environment-setting.md rename to source/posts/environment-setting.md index 704c465..5130383 100644 --- a/YaeBlog/source/posts/environment-setting.md +++ b/source/posts/environment-setting.md @@ -1,11 +1,11 @@ --- title: 环境配置备忘录 -date: 2022-01-15 20:19:39 +date: 2022-01-15T20:19:39.0000000 tags: - - 技术笔记 -typora-root-url: 环境配置 +- 技术笔记 --- + 电脑上的环境三天两头出问题,写下一个备忘录记录一下电脑上环境的配置过程。 @@ -236,7 +236,7 @@ Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope CurrentUser 再重新运行配置文件就没有问题了。 -![终端预览](6.png) +![终端预览](6.webp) ### PowerShell配置文件 diff --git a/source/posts/environment-setting/6.webp b/source/posts/environment-setting/6.webp new file mode 100644 index 0000000..d6f95ee --- /dev/null +++ b/source/posts/environment-setting/6.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c46691ac920565f3dfc282600f84a39af616e04d87c3b1ba8a2e2aa2b77b9b4d +size 76532 diff --git a/YaeBlog/source/posts/genshin-gacha-1.md b/source/posts/genshin-gacha-1.md similarity index 93% rename from YaeBlog/source/posts/genshin-gacha-1.md rename to source/posts/genshin-gacha-1.md index 5b033de..ea8c77f 100644 --- a/YaeBlog/source/posts/genshin-gacha-1.md +++ b/source/posts/genshin-gacha-1.md @@ -1,11 +1,12 @@ --- title: 原神抽卡研究一 +date: 2022-12-31T13:38:19.0000000 tags: - - 原神 - - 学习资料 -date: 2022-12-31 13:38:19 +- 原神 +- 学习资料 --- + 实际上是“概率论和随机过程”课程的期末小论文。 @@ -14,7 +15,7 @@ date: 2022-12-31 13:38:19 目前在市面上出现了大量以抽奖为核心盈利手段的电子游戏,在这种游戏中,获取游戏中的物品不是明码标价的购买,而是通过参加某种抽奖性质的活动。玩家花费一定金额购买参加活动的机会,每次参加都有一定的概率获得玩家想要获得的物品。在抽奖活动中,游戏设计者还会引入一种被称为 “保底” 的游戏机制:开发者向玩家群体承诺在一定的参加次数之后必然会获得到该物品。例如下面是热门游戏《原神》中获取游戏中角色 “抽奖” 活动的概率公示页面。 -![](./genshin-gacha-1/2022-12-31-13-06-36-image.png) +![](./genshin-gacha-1/2022-12-31-13-06-36-image.webp) 在这个 “抽奖” 活动中,玩家可以得到三种等级的物品,在游戏中分别称为 “五星物品”、“四星物品” 和 “三星物品”。在每次的 “抽奖” 活动中,玩家必定会获得上述三种物品中的一种。为了简化问题的讨论,我们现不区分相同等级不同物品之间的不同,只考虑不同星级物品的获取概率。通过概率公示可以知道:五星物品的 “基础概率” 为0.600%,四星物品的 “基础概率” 为 5.100%;五星物品的 “综合概率” 为 1.600%,四星物品的 “综合概率” 为 13.000%。从一个玩家的角度出发,自然会存在两个问题: @@ -50,11 +51,11 @@ date: 2022-12-31 13:38:19 在总共 4842256 次抽卡记录中,获得五星物品的次数为 78493 次,于是: -![](genshin-gacha-1/2022-12-31-15-59-20-image.png) +![](genshin-gacha-1/2022-12-31-15-59-20-image.webp) 再计算一下每次参加该活动获得四星物品的平均概率:在总共 5000139 次抽卡中,获得四星物品的次数为 653200: -![](genshin-gacha-1/2022-12-31-15-59-42-image.png) +![](genshin-gacha-1/2022-12-31-15-59-42-image.webp) 不难发现,在误差允许的范围内,计算出来的平均概率和游戏开发者所公布的 “综合概率” 是相同的。当参与这个游戏足够多次时,获取到五星物品和四星物品的数量就可以用这个概率来估计。 @@ -62,15 +63,15 @@ date: 2022-12-31 13:38:19 为了方便讨论,再次将这个 “游戏” 简化为获得五星物品和不获得五星物品两种情况。那么这个 “抽奖” 游戏是否就能被简化为一个概率为 1.6% 的 n 次伯努利实验?不妨假设每次获得五星物品之间相互独立,这样每次获得五星物品都可以认为是首次获得五星物品,这时参加该游戏的次数就会符合概率为 1.6% 的几何分布,而为了符合保底规则,当玩家在参与到第 90 次时仍未获得五星物品,强制给予玩家一个五星的物品。画出实际数据中得到的图像和按照几何分布得到的图像。 -![](genshin-gacha-1/2022-12-31-13-20-46-image.png) +![](genshin-gacha-1/2022-12-31-13-20-46-image.webp) -![](genshin-gacha-1/2022-12-31-13-21-11-image.png) +![](genshin-gacha-1/2022-12-31-13-21-11-image.webp) 不难发现假设的猜想和实际情况不相符合。在抽数小于 73 抽时,获得五星物品的概率逐渐降低,从 0.6% 左右一直降低至 0.4% 左右。当抽数大于等于 73 抽时,抽到的概率开始上升,在抽数等于 77 抽时达到最大,大约为 10.4%。随后概率开始下降,在第 91 抽时,概率等于 0。 如果在实际得到的概率关于抽数的图上再作出概率为 0.6% 的几何分布的图像,前73 抽的概率图像和几何分布的图像几乎吻合。也就是说,该抽奖游戏的前 73 抽就是一个符合 P = 0.6% 的几何分布,从第 73 抽开始 “保底” 机制的修正。这就是游戏开发者口中 “基础概率” 的含义:在该抽奖游戏的前数十抽就是一个概率为 0.6% 的伯努利实验。 -![](genshin-gacha-1/2022-12-31-13-24-26-image.png) +![](genshin-gacha-1/2022-12-31-13-24-26-image.webp) 从大量的实际数据出发,不难发现游戏开发者的申明同实际情况相吻合。 @@ -80,7 +81,7 @@ date: 2022-12-31 13:38:19 直到作者开始写作本文之前,作者都没有意识到本文 3.2 节中的所有结论几乎都基于该假设。限于文章的篇幅原因和个人的能力问题,在本文中未对这个假设作出验证。下面给出一种验证该猜想的方法。为验证获得五星物品之间相互独立,可以通过统计方法验证下面的等式成立。 -![](genshin-gacha-1/2022-12-31-16-00-10-image.png) +![](genshin-gacha-1/2022-12-31-16-00-10-image.webp) ### 展望 diff --git a/source/posts/genshin-gacha-1/2022-12-31-13-06-36-image.webp b/source/posts/genshin-gacha-1/2022-12-31-13-06-36-image.webp new file mode 100644 index 0000000..dedf95f --- /dev/null +++ b/source/posts/genshin-gacha-1/2022-12-31-13-06-36-image.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e66d8f534067205e66b62036ce7b1e79e82bf3ac2e87f85cc3c45eb54620ca9 +size 61390 diff --git a/source/posts/genshin-gacha-1/2022-12-31-13-20-46-image.webp b/source/posts/genshin-gacha-1/2022-12-31-13-20-46-image.webp new file mode 100644 index 0000000..0b74140 --- /dev/null +++ b/source/posts/genshin-gacha-1/2022-12-31-13-20-46-image.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:827e460e1fb07ad40f7225c26e887b5dd7c107b18651a8150693a3a221c8544b +size 22780 diff --git a/source/posts/genshin-gacha-1/2022-12-31-13-21-11-image.webp b/source/posts/genshin-gacha-1/2022-12-31-13-21-11-image.webp new file mode 100644 index 0000000..2bc2a1d --- /dev/null +++ b/source/posts/genshin-gacha-1/2022-12-31-13-21-11-image.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f484ca74739d3ae8724a7905d0c3ea353ad4a83e6a8ffb92dced9d37fab3c03 +size 19342 diff --git a/source/posts/genshin-gacha-1/2022-12-31-13-24-26-image.webp b/source/posts/genshin-gacha-1/2022-12-31-13-24-26-image.webp new file mode 100644 index 0000000..bdd8dc6 --- /dev/null +++ b/source/posts/genshin-gacha-1/2022-12-31-13-24-26-image.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78e2153061ee743bfdf651d33fd8c1587238aa4a5f827c9c8e471fae278e79ad +size 26180 diff --git a/source/posts/genshin-gacha-1/2022-12-31-15-59-20-image.webp b/source/posts/genshin-gacha-1/2022-12-31-15-59-20-image.webp new file mode 100644 index 0000000..879b8ba --- /dev/null +++ b/source/posts/genshin-gacha-1/2022-12-31-15-59-20-image.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b416dcbb9b458e9e1594de9aac004145e33af82a679f802301659be7af19564a +size 7328 diff --git a/source/posts/genshin-gacha-1/2022-12-31-15-59-42-image.webp b/source/posts/genshin-gacha-1/2022-12-31-15-59-42-image.webp new file mode 100644 index 0000000..b45503c --- /dev/null +++ b/source/posts/genshin-gacha-1/2022-12-31-15-59-42-image.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c1a3c34ee5b7309dc2c638e540f4e933d6354aeb4b47d9f7099bfae41768df7 +size 6974 diff --git a/source/posts/genshin-gacha-1/2022-12-31-16-00-10-image.webp b/source/posts/genshin-gacha-1/2022-12-31-16-00-10-image.webp new file mode 100644 index 0000000..3547a77 --- /dev/null +++ b/source/posts/genshin-gacha-1/2022-12-31-16-00-10-image.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3b9987a736145d91614f59dac655fc9eacd390cabfb2c4cdc684c6f1cce36af +size 10434 diff --git a/YaeBlog/source/posts/heterogeneous-programming-model.md b/source/posts/heterogeneous-programming-model.md similarity index 98% rename from YaeBlog/source/posts/heterogeneous-programming-model.md rename to source/posts/heterogeneous-programming-model.md index 7e5a702..5b9511a 100644 --- a/YaeBlog/source/posts/heterogeneous-programming-model.md +++ b/source/posts/heterogeneous-programming-model.md @@ -7,6 +7,7 @@ tags: --- + 随着摩尔定律的逐渐失效,将CPU和其他架构的计算设备集成在片上或者通过高速总线互联构建的异构系统成为了高性能计算的主流。但是在这种系统中,上层应用的设计与实现面临着异构系统中各个设备之间体系结构差异过大、缺乏良好的异构抽象以及统一的编程接口和应用程序的优化难度大等困难。 异构并行编程模型便是解决这些编程和执行效率问题的解决方案。 @@ -25,11 +26,11 @@ tags: 首先是异构系统中各个设备之间的并行计算能力不同。在同构的并行计算系统中,比如多核CPU中,虽然同一CPU的不同核之间、同一核的不同SIMD部件之间可以承担不同粒度的并行计算任务,但是其并行计算的能力是完全相同的。但是在一个典型的异构计算系统,例如CPU、GPU和FPGA组成的异构系统,不同设备的微架构具有本质差异,其并行计算的模式和能力都完全不同,设备之间的特长也完全不同。这种设备之间并行计算能力的差异使得系统中的任务划分和任务映射不再是均一的,而是具有显著的特异性。这种特点虽然也有利于表达实际应用的特点,但是却给异构并行计算模型的设计带来了巨大的困难。 -![9eb06d8be92ddef3db33e040163c67a7.png](./heterogeneous-programming-model/9eb06d8be92ddef3db33e040163c67a7.png) +![9eb06d8be92ddef3db33e040163c67a7.webp](./heterogeneous-programming-model/9eb06d8be92ddef3db33e040163c67a7.webp) 其次是异构系统中加速设备数据分布可配置、设备间数据通信渠道多样性给数据分布和通信带来的困难。在同构并行系统中,CPU片内的存储是对于软件透明的缓存架构,在片外则是一个共享内存模型,因此在这类系统中,数据仅可能分布在片外的共享存储中,具有存储位置单一的特点,也不需要进行显式的通信操作。但是在异构系统中,不仅在单个加速设备内部可能有软件可分配的快速局部存储,设备之间的连接方式差异也很大。目前,大多个加速设备都是通过PCIe总线的方式同CPU进行连接,这使得加速设备无法通过和CPU相同的方式完成地址映射,存在某一设备无法访问另一设备片外存储的问题。这使得异构系统中数据可以分布在CPU、加速设备的片外存储和加速设备的片内多层次局部存储等多个位置,不仅使得编程模型的数据分布问题变得十分复杂,设备间的通信文件也可能需要显式进行。 -![eab553f9e30d8d866a1ddd201b5e4c85.png](./heterogeneous-programming-model/eab553f9e30d8d866a1ddd201b5e4c85.png) +![eab553f9e30d8d866a1ddd201b5e4c85.webp](./heterogeneous-programming-model/eab553f9e30d8d866a1ddd201b5e4c85.webp) 最后是异构系统中多层次数据共享和多范围同步操作带来的同步困难问题。这也可以认为是上个数据同步问题带来的后继问题:在异构系统中数据可能分布在不同位置的条件下,同步操作需要在众多的位置上保证共享数据的一致性,这使得同步操作的范围变得十分复杂。同时,在一些特定的加速设备中,例如GPU,可能还会有局部的硬件同步机制,这更加提高了在异构系统的同步操作的设计和实现难度。 @@ -47,7 +48,7 @@ tags: 从异构并行编程接口的功能角度上来说也可以分成两类:有些接口屏蔽了较多的异构并行编程细节,通常仅给程序员提供显式异构任务划分的机制,而数据分布和通信、同步等的工作由运行时系统负责完成,也有些接口将多数异构系统的硬件细节通过上述机制暴露给程序员使用,这在给编程带来更大自由度的同时带来了使用上的困难。 -![83ee1d254d638536d0fb4197ff63e758.png](./heterogeneous-programming-model/83ee1d254d638536d0fb4197ff63e758.png) +![83ee1d254d638536d0fb4197ff63e758.webp](./heterogeneous-programming-model/83ee1d254d638536d0fb4197ff63e758.webp) ### 异构任务划分机制研究 @@ -125,7 +126,7 @@ public class Result 采用显示异步数据分布和通信机制的主要问题是普通程序员一般无法充分利用这些接口获得性能上的提升。这通常使用因为加速设备通常采用了大量的硬件加速机制,例如GPU的全局内存访存合并机制,这使得程序员如果没有为数据分配合理的存储位置或者设定足够多的线程,会使得加速的效果大打折扣。因此出现了针对这类显式控制语言的优化方法,例如`CUDA-lite`,这个运行时允许程序元在CUDA程序中加入简单的制导语句,数据分布的相关工作使用`CUDA-lite`的运行时系统完成,降低了CUDA程序的编写难度。 -![628804b3fe95d39013ff55ae84516d14.png](./heterogeneous-programming-model/Screenshot_20241016_214139.png) +![628804b3fe95d39013ff55ae84516d14.png](./heterogeneous-programming-model/Screenshot_20241016_214139.webp) 总结一下,为了解决异构系统带来的问题,异构并行编程接口具有如下三个特点: - 异构任务划分机制在传统并行编程模型的基础上增加了"异构特征描述"的维度,用于描述任务在不同设备上的分配情况; @@ -138,7 +139,7 @@ public class Result 异构编程/运行时系统的任务映射机制主要有两种:一类是直接映射,即独立完成并行任务向异构平台映射的工作,另一种是间接映射,即需要借助其他异构编译和运行时系统协助来完成部分任务映射工作。直接映射系统一般在运行时系统中实现,而间接映射通过源到源变换和是运行时分析相结合的方式实现。 -![](./heterogeneous-programming-model/Screenshot_20241016_214939.png) +![](./heterogeneous-programming-model/Screenshot_20241016_214939.webp) ### 异构编译/运行时优化 @@ -456,13 +457,13 @@ std::vector> cudaCalculateMatrix(const std::vector OpenACCCpuCalculateMatrix(const std::vector& a, con oneAPI是Intel公司提出的一套异构并行编程框架,该框架致力于达成如下几个目标:(1)定义一个跨架构、跨制造商的统一开放软件平台;(2)允许同一套代码可以在不同硬件制造商和加速技术的硬件上运行;(3)提供一套覆盖多个编程领域的库API。为了实现这些目标,oneAPI同上文中已经提到过的开放编程标准SYCL紧密合作,oneAPI也提供了一个SYCL的编译器和运行时;同时oneAPI也提供了一系列API库,包括`oneDPL`、`oneDNN`、`oneTBB`和`oneMKL`等。 -![image-20241103162259981](./heterogeneous-programming-model/image-20241103162259981.png) +![image-20241103162259981](./heterogeneous-programming-model/image-20241103162259981.webp) 我对于oneAPI的理解就是Intel用来对标NVIDIA的CUDA的一套高性能编程工具箱。首先为了和NVIDIA完全闭源的CUDA形成鲜明的对比,Intel选择了OpenCL合作同时开发SYCL,当时也有可能是Intel知道自己的显卡技不如人,如果不兼容市面上其他的部件是没有出路的,同时为了和CUDA丰富的生态竞争,Intel再开发并开源了一系列的`oneXXX`。 diff --git a/source/posts/heterogeneous-programming-model/83ee1d254d638536d0fb4197ff63e758.webp b/source/posts/heterogeneous-programming-model/83ee1d254d638536d0fb4197ff63e758.webp new file mode 100644 index 0000000..79f47f1 --- /dev/null +++ b/source/posts/heterogeneous-programming-model/83ee1d254d638536d0fb4197ff63e758.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c72b55977fde8f705070e25bb2b3a1c947a590931762b4b24fee61237541a13 +size 42802 diff --git a/source/posts/heterogeneous-programming-model/9eb06d8be92ddef3db33e040163c67a7.webp b/source/posts/heterogeneous-programming-model/9eb06d8be92ddef3db33e040163c67a7.webp new file mode 100644 index 0000000..92c9b83 --- /dev/null +++ b/source/posts/heterogeneous-programming-model/9eb06d8be92ddef3db33e040163c67a7.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c2e4123d8743d38b3815c1e664eb6993995e887ca06aa0ff5ae1d17c5110b71 +size 41612 diff --git a/source/posts/heterogeneous-programming-model/Screenshot_20241016_214139.webp b/source/posts/heterogeneous-programming-model/Screenshot_20241016_214139.webp new file mode 100644 index 0000000..bf033cb --- /dev/null +++ b/source/posts/heterogeneous-programming-model/Screenshot_20241016_214139.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50958529b872e918e157e4d2ac88fcf75217d42fea5a1a15dfc1838da5fa224e +size 55040 diff --git a/source/posts/heterogeneous-programming-model/Screenshot_20241016_214939.webp b/source/posts/heterogeneous-programming-model/Screenshot_20241016_214939.webp new file mode 100644 index 0000000..105b241 --- /dev/null +++ b/source/posts/heterogeneous-programming-model/Screenshot_20241016_214939.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f18f28e72f57c13dc8cace6c4a37ecd62158cbfe5f3be8bb745205f2f9aa5fd +size 26892 diff --git a/source/posts/heterogeneous-programming-model/eab553f9e30d8d866a1ddd201b5e4c85.webp b/source/posts/heterogeneous-programming-model/eab553f9e30d8d866a1ddd201b5e4c85.webp new file mode 100644 index 0000000..9ef6e21 --- /dev/null +++ b/source/posts/heterogeneous-programming-model/eab553f9e30d8d866a1ddd201b5e4c85.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72f3ba7b8137833bf5eafe8b643f4b71bf6ee32a2a88245d488fb115037eae44 +size 51792 diff --git a/source/posts/heterogeneous-programming-model/image-20241020142938110.webp b/source/posts/heterogeneous-programming-model/image-20241020142938110.webp new file mode 100644 index 0000000..1fff52f --- /dev/null +++ b/source/posts/heterogeneous-programming-model/image-20241020142938110.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbf1af9f0b6b77ce5ec5b66ec9e19703d5b60ad842c34902ec742bd4ee9eedc7 +size 22878 diff --git a/source/posts/heterogeneous-programming-model/image-20241020155656219.webp b/source/posts/heterogeneous-programming-model/image-20241020155656219.webp new file mode 100644 index 0000000..0322a6c --- /dev/null +++ b/source/posts/heterogeneous-programming-model/image-20241020155656219.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e7f3eb7bead9696d1251074e186b32f8cb5fad135301f6a94ee4e1fff906a4a +size 23790 diff --git a/source/posts/heterogeneous-programming-model/image-20241029123308139.webp b/source/posts/heterogeneous-programming-model/image-20241029123308139.webp new file mode 100644 index 0000000..b56a502 --- /dev/null +++ b/source/posts/heterogeneous-programming-model/image-20241029123308139.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c45d9932eb3b021a76cecb0367794c3c86862d7000d74a8dba4e78eca1834fe +size 43252 diff --git a/source/posts/heterogeneous-programming-model/image-20241029163654675.webp b/source/posts/heterogeneous-programming-model/image-20241029163654675.webp new file mode 100644 index 0000000..933432d --- /dev/null +++ b/source/posts/heterogeneous-programming-model/image-20241029163654675.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94c23eb2289265f6e1181e3b4dd151bfeb052cb54b0ee762170fd49c4aa1cbcd +size 45140 diff --git a/source/posts/heterogeneous-programming-model/image-20241103162259981.webp b/source/posts/heterogeneous-programming-model/image-20241103162259981.webp new file mode 100644 index 0000000..02c9d76 --- /dev/null +++ b/source/posts/heterogeneous-programming-model/image-20241103162259981.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64d6044e0cfd593262f335709a0a50f203da186c6967fda36e9d16b88ff69c21 +size 17042 diff --git a/source/posts/hpc-2025-cpu-architecture.md b/source/posts/hpc-2025-cpu-architecture.md new file mode 100644 index 0000000..b454fc9 --- /dev/null +++ b/source/posts/hpc-2025-cpu-architecture.md @@ -0,0 +1,101 @@ +--- +title: High Performance Computing 25 SP CPU Architecture +date: 2025-03-13T23:59:08.8167680+08:00 +tags: +- 学习资料 +- 高性能计算 +--- + + +How to use the newly available transistors? + + + +Parallelsim: + +Instruction Level Parallelism(ILP): + +- **Implicit/transparent** to users/programmers. +- Instruction pipelining. +- Superscalar execution. +- Out of order execution. +- Register renaming. +- Speculative execution. +- Branch prediction. + +Task Level Parallelism(TLP): + +- **Explicit** to users/programmers. +- Multiple threads or processes executed simultaneously. +- Multi-core processors. + +Data Parallelism: + +- Vector processors and SIMD. + +Von Neumann Architecture: the **stored-program** concept. Three components: processor, memory and data path. + +Bandwidth: the gravity of modern computer system. + +## Instruction Pipelining + +Divide incoming instructions into a series of sequential steps performed by different processor unit to keep every part of the processor busy. + +Superscalar execution can execute more than one instruction during a clock cycle. + +Order of order execution. + +Very long instruction word(VLIW): allows programs to explicitly specify instructions to execute at the same time. + +EPIC: Explicit parallel instruction computing. + +Move the complexity of instruction scheduling from the CPU hardware to the software compiler: + +- Check dependencies between instructions. +- Assign instructions to the functional units. +- Determine when instructions are initiated placed together into a single word. + +![image-20250313184421305](./hpc-2025-cpu-architecture/image-20250313184421305.webp) + +Comparisons between different architecture: + +![image-20250313184732892](./hpc-2025-cpu-architecture/image-20250313184732892.webp) + +## Multi-Core Processor Gala + +Symmetric multiprocessing(SMP): a multiprocessor computer hardware and software architecture. + +Two or more identical processors are connected to a **single shared main memory** and have full access to all input and output devices. + +> Current trend: computer clusters, SMP computers connected with network. + +Multithreading: exploiting thread-level parallelism. + +Multithreading allows multiple threads to share the functional units of a single processor in an overlapping fashion **duplicating only private state**. A thread switch should be much more efficient than a process switch. + +Hardware approaches to multithreading: + +**fine-grained multithreading**: + +- Switches between threads on each clock. +- Hide the throughput losses that arise from the both short and long stalls. +- Disadvantages: slow down the execution of an individual thread. + +**Coarse-grained multithreading**: + +- Switch threads only on costly stalls. +- Limited in its ability to overcome throughput losses + +**Simultaneous multithreading(SMT)**: + +- A variation on fine-grained multithreading + +![image-20250313190913475](./hpc-2025-cpu-architecture/image-20250313190913475.webp) + +## Data Parallelism: Vector Processors + +Provides high-level operations that work on vectors. + +Length of the array also varies depending on hardware. + +SIMD and its generalization in vector parallelism approach improved efficiency by the same operation be performed on multiple data elements. diff --git a/source/posts/hpc-2025-cpu-architecture/image-20250313184421305.webp b/source/posts/hpc-2025-cpu-architecture/image-20250313184421305.webp new file mode 100644 index 0000000..88093ce --- /dev/null +++ b/source/posts/hpc-2025-cpu-architecture/image-20250313184421305.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da1fa5c5f18b966c14647f38cdc0007495a02bfe3d0d4739c6d5fe6ff8bf3b85 +size 18176 diff --git a/source/posts/hpc-2025-cpu-architecture/image-20250313184732892.webp b/source/posts/hpc-2025-cpu-architecture/image-20250313184732892.webp new file mode 100644 index 0000000..468e858 --- /dev/null +++ b/source/posts/hpc-2025-cpu-architecture/image-20250313184732892.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0acec6c138987bd7783f798c096f02cc46745435e02afd476c9ea1c501a7fd4 +size 46450 diff --git a/source/posts/hpc-2025-cpu-architecture/image-20250313190913475.webp b/source/posts/hpc-2025-cpu-architecture/image-20250313190913475.webp new file mode 100644 index 0000000..73273c7 --- /dev/null +++ b/source/posts/hpc-2025-cpu-architecture/image-20250313190913475.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bf11c172400f6a5b8eb54e3922baedbbc38f9da72e4d852898515106d992b99 +size 15492 diff --git a/source/posts/hpc-2025-cuda.md b/source/posts/hpc-2025-cuda.md new file mode 100644 index 0000000..bf76da6 --- /dev/null +++ b/source/posts/hpc-2025-cuda.md @@ -0,0 +1,370 @@ +--- +title: High Performance Computing 25 SP NVIDIA +date: 2025-08-31T13:50:42.8639950+08:00 +tags: +- 高性能计算 +- 学习资料 +--- + + + +Fxxk you, NVIDIA! + + + +CPU/GPU Parallelism: + +Moore's Law gives you more and more transistors: + +- CPU strategy: make the workload (one compute thread) run as fast as possible. +- GPU strategy: make the workload (as many threads as possible) run as fast as possible. + +GPU Architecture: + +- Massively Parallel +- Power Efficient +- Memory Bandwidth +- Commercially Viable Parallelism +- Not dependent on large caches for performance + +![image-20250424192311202](./hpc-2025-cuda/image-20250424192311202.webp) + +## Nvidia GPU Generations + +- 2006: G80-based GeForce 8800 +- 2008: GT200-based GeForce GTX 280 +- 2010: Fermi +- 2012: Kepler +- 2014: Maxwell +- 2016: Pascal +- 2017: Volta +- 2021: Ampere +- 2022: Hopper +- 2024: Blackwell + +#### 2006: G80 Terminology + +SP: Streaming Processor, scalar ALU for a single CUDA thread + +SPA: Stream Processor Array + +SM: Streaming Multiprocessor, containing of 8 SP + +TPC: Texture Processor Cluster: 2 SM + TEX + +![image-20250424192825010](./hpc-2025-cuda/image-20250424192825010.webp) + +Design goal: performance per millimeter + +For GPUs, performance is throughput, so hide latency with computation not cache. + +So this is single instruction multiple thread (SIMT). + +**Thread Life Cycle**: + +Grid is launched on the SPA and thread blocks are serially distributed to all the SM. + +![image-20250424193125125](./hpc-2025-cuda/image-20250424193125125.webp) + +**SIMT Thread Execution**: + +Groups of 32 threads formed into warps. Threads in the same wraps always executing same instructions. And some threads may become inactive when code path diverges so the hardware **automatically Handles divergence**. + +Warps are the primitive unit of scheduling. + +> SIMT execution is an implementation choice. As sharing control logic leaves more space for ALUs. + +**SM Warp Scheduling**: + +SM hardware implements zero-overhead warp scheduling: + +- Warps whose next instruction has its operands ready for consumption are eligible for execution. +- Eligible warps are selected for execution on a prioritized scheduling policy. + +> If 4 clock cycles needed to dispatch the same instructions for all threads in a warp, and one global memory access is needed for every 4 instructions and memory latency is 200 cycles. So there should be 200 / (4 * 4) =12.5 (13) warps to fully tolerate the memory latency + +The SM warp scheduling use scoreboard and similar things. + +**Granularity Consideration**: + +Consider that int the G80 GPU, one SM can run 768 threads and 8 thread blocks, which is the best tiles to matrix multiplication: 16 * 16 = 256 and in one SM there can be 3 thread block which fully use the threads. + +### 2008: GT200 Architecture + +![image-20250424195111341](./hpc-2025-cuda/image-20250424195111341.webp) + +### 2010: Fermi GF100 GPU + +**Fermi SM**: + +![image-20250424195221886](./hpc-2025-cuda/image-20250424195221886.webp) + +There are 32 cores per SM and 512 cores in total, and introduce 64KB configureable L1/ shared memory. + +Decouple internal execution resource and dual issue pipelines to select two warps. + +And in Fermi, the debut the Parallel Thread eXecution(PTX) 2.0 ISA. + +### 2012 Kepler GK 110 + +![image-20250424200022880](./hpc-2025-cuda/image-20250424200022880.webp) + +### 2014 Maxwell + +4 GPCs and 16 SMM. + +![image-20250424200330783](./hpc-2025-cuda/image-20250424200330783.webp) + +### 2016 Pascal + +No thing to pay attention to. + +### 2017 Volta + +First introduce the tensor core, which is the ASIC to calculate matrix multiplication. + +### 2021 Ampere + +The GA100 SM: + +![image-20250508183446257](./hpc-2025-cuda/image-20250508183446257.webp) + +### 2022 Hopper + +Introduce the GH200 Grace Hopper Superchip: + +![image-20250508183528381](./hpc-2025-cuda/image-20250508183528381.webp) + +A system contains a CPU and GPU which is linked by a NVLink technology. + +And this system can scale out for machine learning. + +![image-20250508183724162](./hpc-2025-cuda/image-20250508183724162.webp) + +Memory access across the NVLink: + +- GPU to local CPU +- GPU to peer GPU +- GPU to peer CPU + +![image-20250508183931464](./hpc-2025-cuda/image-20250508183931464.webp) + +These operations can be handled by hardware accelerated memory coherency. Previously, there are separate page table for CPU and GPU but for GPU to access memory in both CPU and GPU, CPU and GPU can use the same page table. + +![image-20250508184155087](./hpc-2025-cuda/image-20250508184155087.webp) + +### 2025 Blackwell + +![image-20250508184455215](./hpc-2025-cuda/image-20250508184455215.webp) + +### Compute Capability + +The software version to show hardware version features and specifications. + + ## G80 Memory Hierarchy + +### Memory Space + +Each thread can + +- Read and write per-thread registers. +- Read and write per-thread local memory. +- Read and write pre-block shared memory. +- Read and write pre-grid global memory. +- Read only pre-grid constant memory. +- Read only pre-grid texture memory. + +![image-20250508185236920](./hpc-2025-cuda/image-20250508185236920.webp) + +Parallel Memory Sharing: + +- Local memory is per-thread and mainly for auto variables and register spill. +- Share memory is pre-block which can be used for inter thread communication. +- Global memory is pre-application which can be used for inter grid communication. + +### SM Memory Architecture + +![image-20250508185812302](./hpc-2025-cuda/image-20250508185812302.webp) + +Threads in a block share data and results in memory and shared memory. + +Shared memory is dynamically allocated to blocks which is one of the limiting resources. + +### SM Register File + +Register File(RF): there are 32KB, or 8192 entries, register for each SM in G80 GPU. + +The tex pipeline and local/store pipeline can read and write register file. + +Registers are dynamically partitioned across all blocks assigned to the SM. Once assigned to a block the register is **not** accessible by threads in other blocks and each thread in the same block only access registers assigned to itself. + +For a matrix multiplication example: + +- If one thread uses 10 registers and one block has 16x16 threads, each SM can contains three thread blocks as one thread blocks need 16 * 16 * 10 =2,560 registers and 3 * 2560 < 8192. +- But if each thread need 11 registers, one SM can only contains two blocks once as 8192 < 2816 * 3. + +More on dynamic partitioning: dynamic partitioning gives more flexibility to compilers and programmers. + +1. A smaller number of threads that require many registers each. +2. A large number of threads that require few registers each. + +So there is a tradeoff between instruction level parallelism and thread level parallelism. + +### Parallel Memory Architecture + +In a parallel machine, many threads access memory. So memory is divided into banks to achieve high bandwidth. + +Each bank can service one address per cycle. If multiple simultaneous accesses to a bank result in a bank conflict. + +Shared memory bank conflicts: + +- The fast cases: + - All threads of a half-warp access different banks, there's no back conflict. + - All threads of a half-warp access the identical address ,there is no bank conflict (by broadcasting). +- The slow cases: + - Multiple threads in the same half-warp access the same bank + +## Memory in Later Generations + +### Fermi Architecture + +**Unified Addressing Model** allows local, shared and global memory access using the same address space. + +![image-20250508193756274](./hpc-2025-cuda/image-20250508193756274.webp) + +**Configurable Caches** allows programmers to configure the size if L1 cache and the shared memory. + +The L1 cache works as a counterpart to shared memory: + +- Shared memory improves memory access for algorithms with well defined memory access. +- L1 cache improves memory access for irregular algorithms where data addresses are not known before hand. + +### Pascal Architecture + +**High Bandwidth Memory**: a technology which enables multiple layers of DRAM components to be integrated vertically on the package along with the GPU. + +![image-20250508194350572](./hpc-2025-cuda/image-20250508194350572.webp) + +**Unified Memory** provides a single and unified virtual address space for accessing all CPU and GPU memory in the system. + +And the CUDA system software doesn't need to synchronize all managed memory allocations to the GPU before each kernel launch. This is enabled by **memory page faulting**. + +## Advanced GPU Features + +### GigaThread + +Enable concurrent kernel execution: + +![image-20250508195840957](./hpc-2025-cuda/image-20250508195840957.webp) + +And provides dual **Streaming Data Transfer** engines to enable streaming data transfer, a.k.a direct memory access. + +![image-20250508195938546](./hpc-2025-cuda/image-20250508195938546.webp) + +### GPUDirect + +![image-20250508200041910](./hpc-2025-cuda/image-20250508200041910.webp) + +### GPU Boost + +GPU Boost works through real time hardware monitoring as opposed to application based profiles. It attempts to find what is the appropriate GPU frequency and voltage for a given moment in time. + +### SMX Architectural Details + +Each unit contains four warp schedulers. + +Scheduling functions: + +- Register scoreboard for long latency operations. +- Inter-warp scheduling decisions. +- Thread block level scheduling. + +### Improving Programmability + +![image-20250515183524043](./hpc-2025-cuda/image-20250515183524043.webp) + +**Dynamic Parallelism**: The ability to launch new grids from the GPU. + +And then introduce data-dependent parallelism and dynamic work generation and even batched and nested parallelism. + +The cpu controlled work batching: + +- CPU program limited by single point of control. +- Can run at most 10s of threads. +- CPU is fully consumed with controlling launches. + +![](./hpc-2025-cuda/image-20250515184225475.webp) + +Batching via dynamic parallelism: + +- Move top-level loops to GPUs. +- Run thousands of independent tasks. +- Release CPU for other work. + +![image-20250515184621914](./hpc-2025-cuda/image-20250515184621914.webp) + +### Grid Management Unit + +![image-20250515184714663](./hpc-2025-cuda/image-20250515184714663.webp) + +Fermi Concurrency: + +- Up to 16 grids can run at once. +- But CUDA streams multiplex into a single queue. +- Overlap only at stream edge. + +Kepler Improved Concurrency: + +- Up to 32 grids can run at once. +- One work queue per stream. +- Concurrency at full-stream level. +- No inter-stream dependencies. + +It is called as **Hyper-Q**. + +Without Hyper-Q: + +![image-20250515185019590](./hpc-2025-cuda/image-20250515185019590.webp) + +With Hyper-Q: + +![image-20250515185034758](./hpc-2025-cuda/image-20250515185034758.webp) + +In pascal, **asynchronous concurrent computing** is introduced. + +![image-20250515185801775](./hpc-2025-cuda/image-20250515185801775.webp) + +### NVLink: High-Speed Node Network + +![image-20250515185212184](./hpc-2025-cuda/image-20250515185212184.webp) + +> The *consumer* prefix means the product is designed for gamers. +> +> The *big* prefix means the product is designed for HPC. + +### Preemption + +Pascal can actually preempt at the lowest level, the instruction level. + +![image-20250515190244112](./hpc-2025-cuda/image-20250515190244112.webp) + +### Tensor Core + +Operates on a 4x4 matrix and performs: D = A x B + C. + +![image-20250515190507199](./hpc-2025-cuda/image-20250515190507199.webp) + +### GPU Multi-Process Scheduling + +- Timeslice scheduling: single process throughput optimization. +- Multi process service: multi-process throughput optimization. + +How about multi-process time slicing: + +![image-20250515190703918](./hpc-2025-cuda/image-20250515190703918.webp) + +Volta introduces the multi-process services: + +![image-20250515191142384](./hpc-2025-cuda/image-20250515191142384.webp) + + diff --git a/source/posts/hpc-2025-cuda/image-20250424192311202.webp b/source/posts/hpc-2025-cuda/image-20250424192311202.webp new file mode 100644 index 0000000..e96dddc --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250424192311202.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f0e111666fbf2c927e4852cdbb4ec6e06ba440351f6ffd3e67413e0f3963ee0 +size 15530 diff --git a/source/posts/hpc-2025-cuda/image-20250424192825010.webp b/source/posts/hpc-2025-cuda/image-20250424192825010.webp new file mode 100644 index 0000000..257bc3e --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250424192825010.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eda9be9773e53b9c1b19539c753176a421a4705fcdfb47001c0467d85a9239f2 +size 15208 diff --git a/source/posts/hpc-2025-cuda/image-20250424193125125.webp b/source/posts/hpc-2025-cuda/image-20250424193125125.webp new file mode 100644 index 0000000..5a6b1ca --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250424193125125.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c307955cd05a6eeddd6aef30b57538775750e178417856d0c2ec973630e935fe +size 17696 diff --git a/source/posts/hpc-2025-cuda/image-20250424195111341.webp b/source/posts/hpc-2025-cuda/image-20250424195111341.webp new file mode 100644 index 0000000..d71fbd2 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250424195111341.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a633737fc0b834fad87818ee98c2e14d4dd42e2e0645a74876f9871d48b47168 +size 68264 diff --git a/source/posts/hpc-2025-cuda/image-20250424195221886.webp b/source/posts/hpc-2025-cuda/image-20250424195221886.webp new file mode 100644 index 0000000..4d4cd81 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250424195221886.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f24b6c69676ade40134c548e8025b5dacd7c9225c7dbfede5a2b9afb3834107e +size 60158 diff --git a/source/posts/hpc-2025-cuda/image-20250424200022880.webp b/source/posts/hpc-2025-cuda/image-20250424200022880.webp new file mode 100644 index 0000000..aaf8943 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250424200022880.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65332083c59ea8e39c1e59822c59bf3050d24cd1e6794774633e31bff5eadcb5 +size 79318 diff --git a/source/posts/hpc-2025-cuda/image-20250424200330783.webp b/source/posts/hpc-2025-cuda/image-20250424200330783.webp new file mode 100644 index 0000000..161f56e --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250424200330783.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bb9274d58e632f46ad8be3321641e4cdbaa4165c3388df8de2d98a0b5b22b3a +size 46682 diff --git a/source/posts/hpc-2025-cuda/image-20250508183446257.webp b/source/posts/hpc-2025-cuda/image-20250508183446257.webp new file mode 100644 index 0000000..b0251d6 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508183446257.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86611fcd51660121820ccdfab5a1db18d412fee98f2029deb8c5109bfa4178dc +size 59112 diff --git a/source/posts/hpc-2025-cuda/image-20250508183528381.webp b/source/posts/hpc-2025-cuda/image-20250508183528381.webp new file mode 100644 index 0000000..f89a78e --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508183528381.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8e8046354b1637dae15d96760ab20587e0c30104958349a928ab7b2901b94e +size 32956 diff --git a/source/posts/hpc-2025-cuda/image-20250508183724162.webp b/source/posts/hpc-2025-cuda/image-20250508183724162.webp new file mode 100644 index 0000000..222b37e --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508183724162.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8ad0bb00f8b9da48e12bdb99f6ce58d810bed9cf72d09f53cf771f1dde04405 +size 66400 diff --git a/source/posts/hpc-2025-cuda/image-20250508183931464.webp b/source/posts/hpc-2025-cuda/image-20250508183931464.webp new file mode 100644 index 0000000..b3ee864 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508183931464.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea7d38b9dcb767203389c4a8e18315e396bb9f899ed7f923520569967b7ea4bf +size 46150 diff --git a/source/posts/hpc-2025-cuda/image-20250508184155087.webp b/source/posts/hpc-2025-cuda/image-20250508184155087.webp new file mode 100644 index 0000000..f9f0125 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508184155087.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40d6fd3963735b8236bbafeec9d58f20ad34057382b9a9689b5dfd188035729a +size 55910 diff --git a/source/posts/hpc-2025-cuda/image-20250508184455215.webp b/source/posts/hpc-2025-cuda/image-20250508184455215.webp new file mode 100644 index 0000000..8385304 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508184455215.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9bcabe4f1f40cbe4023bb898d94fdc85e0691cc7de6b778bb9057f6390d8797 +size 26942 diff --git a/source/posts/hpc-2025-cuda/image-20250508185236920.webp b/source/posts/hpc-2025-cuda/image-20250508185236920.webp new file mode 100644 index 0000000..f39560e --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508185236920.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f89b908ab76ce543c6f6074abbe561a2eae08f4c371f812aa68750920c73e7df +size 16338 diff --git a/source/posts/hpc-2025-cuda/image-20250508185812302.webp b/source/posts/hpc-2025-cuda/image-20250508185812302.webp new file mode 100644 index 0000000..79de09e --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508185812302.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7302d54d63c65c795ebbe8aa7cdf89d18a5c2e435e4aa2a63b9b78960639a814 +size 13960 diff --git a/source/posts/hpc-2025-cuda/image-20250508193756274.webp b/source/posts/hpc-2025-cuda/image-20250508193756274.webp new file mode 100644 index 0000000..ab6c288 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508193756274.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3eecac7e1d084ebd42bdc3272f171cc5ff6a05f01a8038b094165a851ff8dff3 +size 14376 diff --git a/source/posts/hpc-2025-cuda/image-20250508194350572.webp b/source/posts/hpc-2025-cuda/image-20250508194350572.webp new file mode 100644 index 0000000..48fa5a2 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508194350572.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a345cea108f141e30e381088eee11877785dde04720f515d95f002547c452ba1 +size 21504 diff --git a/source/posts/hpc-2025-cuda/image-20250508195840957.webp b/source/posts/hpc-2025-cuda/image-20250508195840957.webp new file mode 100644 index 0000000..c948d0a --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508195840957.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66a6fce8fd76e4c1dfc422d9e52096f8264d77f733269b291d0e24e24a4b0daf +size 13222 diff --git a/source/posts/hpc-2025-cuda/image-20250508195938546.webp b/source/posts/hpc-2025-cuda/image-20250508195938546.webp new file mode 100644 index 0000000..361a803 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508195938546.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:312eac09b66a185f073f88e3b9f4b8e04915ee3d69beedebf4894af00787bb69 +size 53104 diff --git a/source/posts/hpc-2025-cuda/image-20250508200041910.webp b/source/posts/hpc-2025-cuda/image-20250508200041910.webp new file mode 100644 index 0000000..ca1086f --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250508200041910.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:285048b87012a358a7502f7c8c40205824025c2781e36f75ccef6920f1fdf785 +size 24732 diff --git a/source/posts/hpc-2025-cuda/image-20250515183524043.webp b/source/posts/hpc-2025-cuda/image-20250515183524043.webp new file mode 100644 index 0000000..89bc426 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250515183524043.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65d20b3460822b5a5e70e6227738bac2378e56ce0d40bf6ed62bd53857a69266 +size 24048 diff --git a/source/posts/hpc-2025-cuda/image-20250515184225475.webp b/source/posts/hpc-2025-cuda/image-20250515184225475.webp new file mode 100644 index 0000000..8d0d501 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250515184225475.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9da449b1ac9b8087c76f6ca2bc3e23c4186c7cfcb887d115c6e99b5584f5a287 +size 44426 diff --git a/source/posts/hpc-2025-cuda/image-20250515184621914.webp b/source/posts/hpc-2025-cuda/image-20250515184621914.webp new file mode 100644 index 0000000..180c816 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250515184621914.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f77d1908ca8987bb746b670301a51db17f114cbad83b0ca69e85cde18a707d62 +size 28244 diff --git a/source/posts/hpc-2025-cuda/image-20250515184714663.webp b/source/posts/hpc-2025-cuda/image-20250515184714663.webp new file mode 100644 index 0000000..55f5b74 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250515184714663.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb34ca7d6d92dd9e22ae942c8ada1614fd8e25b8095d8ec8307a09082967d76a +size 40020 diff --git a/source/posts/hpc-2025-cuda/image-20250515185019590.webp b/source/posts/hpc-2025-cuda/image-20250515185019590.webp new file mode 100644 index 0000000..9001517 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250515185019590.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ced6350d6187f5b38cd94523f553d7fdeb25d32a3722569f65a4ac2a7da1ba7 +size 14958 diff --git a/source/posts/hpc-2025-cuda/image-20250515185034758.webp b/source/posts/hpc-2025-cuda/image-20250515185034758.webp new file mode 100644 index 0000000..3c19092 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250515185034758.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3a6f1951815e0b76f898cf0c1d67cf011c0b32c5b96830e1de0a857606a92f0 +size 16056 diff --git a/source/posts/hpc-2025-cuda/image-20250515185212184.webp b/source/posts/hpc-2025-cuda/image-20250515185212184.webp new file mode 100644 index 0000000..a9bbc90 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250515185212184.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82ef79a681c49dccc42785b80aaae21e6032b325b06cc2a7ae0622fe1d8ffc99 +size 32912 diff --git a/source/posts/hpc-2025-cuda/image-20250515185801775.webp b/source/posts/hpc-2025-cuda/image-20250515185801775.webp new file mode 100644 index 0000000..840a2f9 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250515185801775.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68ef3bea3c29a29504493127dc5daeb486d4e4f0b4067a7c6ea9c8115f1287d0 +size 46842 diff --git a/source/posts/hpc-2025-cuda/image-20250515190244112.webp b/source/posts/hpc-2025-cuda/image-20250515190244112.webp new file mode 100644 index 0000000..e333e43 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250515190244112.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a11c38b980b05ff40e82fbdc1ed2caccd50fce7ad97433f6a8291f64932a8ab +size 20244 diff --git a/source/posts/hpc-2025-cuda/image-20250515190507199.webp b/source/posts/hpc-2025-cuda/image-20250515190507199.webp new file mode 100644 index 0000000..f840d49 --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250515190507199.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d791e759ea8a00679ba983319ac1e86db6247c9e9c5a32592570a8dcda1ddb2 +size 49348 diff --git a/YaeBlog/source/posts/software-engineer/image-20240621114540033.png b/source/posts/hpc-2025-cuda/image-20250515190703918.webp similarity index 81% rename from YaeBlog/source/posts/software-engineer/image-20240621114540033.png rename to source/posts/hpc-2025-cuda/image-20250515190703918.webp index ec7791d..09e2d9c 100644 --- a/YaeBlog/source/posts/software-engineer/image-20240621114540033.png +++ b/source/posts/hpc-2025-cuda/image-20250515190703918.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8ae17c7517b83ed4429ce148ebfd60595ea94127405117af34bec316abe4956 -size 172404 +oid sha256:a6731401a0cd4fdb33cfbc783fcc870250134aa557cd311154af0f9fcf7a305c +size 47884 diff --git a/source/posts/hpc-2025-cuda/image-20250515191142384.webp b/source/posts/hpc-2025-cuda/image-20250515191142384.webp new file mode 100644 index 0000000..5b88feb --- /dev/null +++ b/source/posts/hpc-2025-cuda/image-20250515191142384.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10864b7445842aaf518ac0c0bd8bca0288dcc71deb22c9a4dfa3b92f6734b92c +size 31454 diff --git a/source/posts/hpc-2025-distributed-system.md b/source/posts/hpc-2025-distributed-system.md new file mode 100644 index 0000000..c3df497 --- /dev/null +++ b/source/posts/hpc-2025-distributed-system.md @@ -0,0 +1,224 @@ +--- +title: High Performance Computing 25 SP Distributed System +date: 2025-05-10T00:31:39.3109950+08:00 +tags: +- 高性能计算 +- 学习资料 +--- + + +The motivation of distributed system is resource sharing. + + + +### Definition of a Distributed System + +- A collection of independent computers that appears to its users as a single coherent system. +- A system in which hardware and software components located at networked computers communicated and coordinate their actions on by message passing. + +Important aspects: + +- Components are autonomous. +- Virtually single systems as **transparency**. + +### Kinds of Systems + +**Clustering**: + +A cluster is a group of independent resources that are interconnected and work as a single system. + +A general prerequisite of hardware clustering is that its component systems have reasonably identical hardware and operating system to provide similar performance levels when one failed component is to be replaced by another. + +**Peer-to-Peer Network**: + +P2P system are quite popular for file sharing, content distribution and Internet telephony. + +**Grid Computing**: + +A computing grid (or *computational grid* ) provides a platform in which computing resources are organized into one or more logical pools. + +**Cloud Computing**: + +Enables clients to outsource their software usage, data storage and even the computing infrastructure to remote data centers. + +![image-20250410193527994](./hpc-2025-distributed-system/image-20250410193527994.webp) + +**Fog Computing**: + +Fog computing focuses processing efforts at the local area network end of the chain. + +**Edge Computing**: + +Edge computing takes localized processing a bit farther, push these efforts closer to the data sources. + +**Near Resources Computing**: + +While CPU becomes powerful, I/O devices too. So offload CPU for domain-specific computing. + +### Features of Distributed System + +**Transparency**: + +- Access transparency. +- Location transparency. +- Migration transparency. +- Relocation transparency. +- Replication transparency. +- Concurrency transparency. +- Failure transparency. + +**Openness**: + +- Open distributed systems: offer services according to standard rules that describe the syntax and semantics of those services. +- Services are specified through *interfaces*. + +**Scalability**: + +Size scalability: more users and more resources. + +- Centralized services: a single server for all users. +- Centralized data: a single on-line telephone book. +- Centralized algorithms: doing routing based on completed information. + +### Common Problems is Distributed Systems + +1. Leader Election +2. Mutual Exclusion +3. Time Synchronization +4. Global State +5. Multicasting +6. Replica Management + +### Time in Distributed Systems + +Atomic clocks: modern timekeepers use atomic clocks as a de facto primary standard of time. + +**Happened Before Relationship**: + +Three basic rules about the causal ordering of events, and they collectively define the *happened before* a.k.a the *causally ordered before* relationship. + +- Rule 1: Let each process have a physical clock whose value is monotonically increasing. +- Rule 2: If *a* is the event of sending a message by process *P*, and *b* is the event of receiving the same message by another process *Q*, so the a < b. +- Rule 3: a < b and b < c can lead to a < c. + +The space time diagrams show such relationship: + +![image-20250417184421464](./hpc-2025-distributed-system/image-20250417184421464.webp) + +**Logical Clocks**: + +A logical clock is an event counter that respects causal ordering. + +**Vector Clocks**: + +The primary goal of vector clocks is to detect causality, which is the major weakness of logical clocks. + +![image-20250424183610157](./hpc-2025-distributed-system/image-20250424183610157.webp) + +![image-20250424183629681](./hpc-2025-distributed-system/image-20250424183629681.webp) + +![image-20250424183645210](./hpc-2025-distributed-system/image-20250424183645210.webp) + +**Synchronization Classification**: + +Types of synchronization: + +- External synchronization +- Internal synchronization +- Phase synchronization + +> Types of clocks: +> +> - Unbounded +> - Bounded +> +> Unbounded clocks are not realistic but are easier to deal with in the design of algorithms. Real clocks are always bounded. + +**External Synchronization**: + +To maintain the reading of each clock as close to the UTC as possible. + +The NTP is an external synchronization protocol. + +**Internal Synchronization**: + +To keep the readings of a system of autonomous clocks closely synchronized with one another, despite the failure or malfunction of one or more clocks. + +Of course external synchronization implies internal synchronization. + +**Phase Synchronization**: + +Many distributed computations run in phases: in a given phase all processes execute some actions which are followed by the next phase. + +## Data Center Organization + +A data center is a facility used to house computer systems and associated components. + +![image-20250417185200176](./hpc-2025-distributed-system/image-20250417185200176.webp) + +## Cloud Computing + +Cloud computing is a specialized form of distributed computing that introduces utilization models for remotely provisioning scalable and measured resources. + +>**NIST definition**: +> +>Cloud computing is a model for enabling ubiquitous, convenient, on-demand network access to a shared pool of configurable computing resources (e.g., networks, servers, storage, applications, and services) that can be rapidly provisioned and released with minimal management effort or service provider interaction. This cloud model is composed of five essential characteristics, three service models, and four deployment models. + +![image-20250417190247790](./hpc-2025-distributed-system/image-20250417190247790.webp) + +**Cloud Characteristics**: + +- On-demand Usage +- Ubiquitous Access +- Multitenancy +- Elasticity +- Measure Usage +- Resiliency + +**Cloud Delivery Models**: + +A cloud service delivery model represents a specific pre-packaged combination of IT resources offered by a cloud provider. + +- Infrastructure as a Service `IaaS` +- Platform as a a Service `PaaS` +- Software as a Service `SaaS` + +**Hypervisor**: + +Type 1 hypervisor: + +![image-20250417191509682](./hpc-2025-distributed-system/image-20250417191509682.webp) + +Type 2 hypervisor: + +![image-20250417191526416](./hpc-2025-distributed-system/image-20250417191526416.webp) + +**CPU Virtualization**: + +Inter VT-X and AMD SVM: + +- Introduce virtualization technology processors with an extra instruction set called Virtual Machine Extensions or VMX. +- Add additional operating model for host and guest. +- Support for swapping state between guest and host. +- Support for hiding privileged state. + +![image-20250417192453944](./hpc-2025-distributed-system/image-20250417192453944.webp) + +## Big Data Processing + +**MapReduce Programming Model** + +MapReduce is based on a very simple idea for parallel processing of data-intensive applications supporting arbitrarily divisible load sharing. + +> The so-called same process multiple data (SPMD) paradigm. + +**MapReduce Logical Data Flow**: + +The input data and output data of both the Map and reduce functions has a particular structure. + +Sending computation toward data rather than sending data toward computation. + +**Resilient Distributed Dataset** + +An RDD is a read-only partitioned collection of records. + diff --git a/source/posts/hpc-2025-distributed-system/image-20250410193527994.webp b/source/posts/hpc-2025-distributed-system/image-20250410193527994.webp new file mode 100644 index 0000000..aab3726 --- /dev/null +++ b/source/posts/hpc-2025-distributed-system/image-20250410193527994.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87b35cff45f7a9236e4f7ba5420bb55e10089a94c902d429e3d49028acd992ff +size 40522 diff --git a/source/posts/hpc-2025-distributed-system/image-20250417184421464.webp b/source/posts/hpc-2025-distributed-system/image-20250417184421464.webp new file mode 100644 index 0000000..bb2b191 --- /dev/null +++ b/source/posts/hpc-2025-distributed-system/image-20250417184421464.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:deee98ae1d92208ce8eb558b683b874d2591ae7e638a4aa7d8f3f39364cb069e +size 25654 diff --git a/source/posts/hpc-2025-distributed-system/image-20250417185200176.webp b/source/posts/hpc-2025-distributed-system/image-20250417185200176.webp new file mode 100644 index 0000000..210a797 --- /dev/null +++ b/source/posts/hpc-2025-distributed-system/image-20250417185200176.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32fef176f981353f1e6e1a55c3775343c1d22ae1b268a89c2788e992f90f8298 +size 28724 diff --git a/source/posts/hpc-2025-distributed-system/image-20250417190247790.webp b/source/posts/hpc-2025-distributed-system/image-20250417190247790.webp new file mode 100644 index 0000000..e6cc808 --- /dev/null +++ b/source/posts/hpc-2025-distributed-system/image-20250417190247790.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a853bc4d218f2b55f0a46200c0e1489f8b37f55ff6927cd012a90b58b7c7341 +size 22476 diff --git a/source/posts/hpc-2025-distributed-system/image-20250417191509682.webp b/source/posts/hpc-2025-distributed-system/image-20250417191509682.webp new file mode 100644 index 0000000..dea8277 --- /dev/null +++ b/source/posts/hpc-2025-distributed-system/image-20250417191509682.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:005a0b076f873a12008ff8e629a17fc0a50c27e709f0f49e385b36c0f9de0e7a +size 18788 diff --git a/source/posts/hpc-2025-distributed-system/image-20250417191526416.webp b/source/posts/hpc-2025-distributed-system/image-20250417191526416.webp new file mode 100644 index 0000000..f71cb05 --- /dev/null +++ b/source/posts/hpc-2025-distributed-system/image-20250417191526416.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9982b1fdb5e51d856bb16201811ae39218885363aa80e9f1b655b22c5c49fc4 +size 47384 diff --git a/source/posts/hpc-2025-distributed-system/image-20250417192453944.webp b/source/posts/hpc-2025-distributed-system/image-20250417192453944.webp new file mode 100644 index 0000000..f0ab3aa --- /dev/null +++ b/source/posts/hpc-2025-distributed-system/image-20250417192453944.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a43f217d5bc520e84d9e3696734b2a3b8a356b0d6270fc33e772e0a051bab0f9 +size 69182 diff --git a/source/posts/hpc-2025-distributed-system/image-20250424183610157.webp b/source/posts/hpc-2025-distributed-system/image-20250424183610157.webp new file mode 100644 index 0000000..370c235 --- /dev/null +++ b/source/posts/hpc-2025-distributed-system/image-20250424183610157.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32c0a5f07df25bb3f3365a6eda2e1a4d9a0b68dbc10526741d1e80c5e9f8d56b +size 36830 diff --git a/source/posts/hpc-2025-distributed-system/image-20250424183629681.webp b/source/posts/hpc-2025-distributed-system/image-20250424183629681.webp new file mode 100644 index 0000000..a98926b --- /dev/null +++ b/source/posts/hpc-2025-distributed-system/image-20250424183629681.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61b577a989170b2a17598c37df0f8675bb4688c9ea3643de360d88366abf4266 +size 65378 diff --git a/source/posts/hpc-2025-distributed-system/image-20250424183645210.webp b/source/posts/hpc-2025-distributed-system/image-20250424183645210.webp new file mode 100644 index 0000000..bcbdfe6 --- /dev/null +++ b/source/posts/hpc-2025-distributed-system/image-20250424183645210.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee1f195051b08d90e9155ea6a1ea9d4ed2cb35f2284e7522da481400f667a4e3 +size 29944 diff --git a/source/posts/hpc-2025-heterogeneous-system.md b/source/posts/hpc-2025-heterogeneous-system.md new file mode 100644 index 0000000..54ded24 --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system.md @@ -0,0 +1,80 @@ +--- +title: High Performance Computing 25 SP Heterogeneous Computing +date: 2025-05-10T00:36:20.5391570+08:00 +tags: +- 高性能计算 +- 学习资料 +--- + + +Heterogeneous Computing is on the way! + + + +## GPU Computing Ecosystem + +CUDA: NVIDIA's Architecture for GPU computing. + +![image-20250417195644624](./hpc-2025-heterogeneous-system/image-20250417195644624.webp) + +## Internal Buses + +**HyperTransport**: + +Primarily a low latency direct chip to chip interconnect, supports mapping to board to board interconnect such as PCIe. + +**PCI Expression** + +Switched and point-to-point connection. + +**NVLink** + +![image-20250417200241703](./hpc-2025-heterogeneous-system/image-20250417200241703.webp) + +**OpenCAPI** + +Heterogeneous computing was in the professional world mostly limited to HPC, in the consumer world is a "nice to have". + +But OpenCAPI is absorbed by CXL. + +## CPU-GPU Arrangement + +![image-20250424184701573](./hpc-2025-heterogeneous-system/image-20250424184701573.webp) + +#### First Stage: Intel Northbrige + +![image-20250424185022360](./hpc-2025-heterogeneous-system/image-20250424185022360.webp) + +### Second Stage: Symmetric Multiprocessors: + +![image-20250424185048036](./hpc-2025-heterogeneous-system/image-20250424185048036.webp) + +### Third Stage: Nonuniform Memory Access + +And the memory controller is integrated directly in the CPU. + +![image-20250424185152081](./hpc-2025-heterogeneous-system/image-20250424185152081.webp) + +So in such context, the multiple CPUs is called NUMA: + +![image-20250424185219673](./hpc-2025-heterogeneous-system/image-20250424185219673.webp) + +And so there can be multi GPUs: + +![image-20250424185322963](./hpc-2025-heterogeneous-system/image-20250424185322963.webp) + +### Fourth Stage: Integrated PCIe in CPU + +![image-20250424185354247](./hpc-2025-heterogeneous-system/image-20250424185354247.webp) + +And there is such team *integrated CPU*, which integrated a GPU into the CPU chipset. + +![image-20250424185449577](./hpc-2025-heterogeneous-system/image-20250424185449577.webp) + +And the integrated GPU can work with discrete GPUs: + +![image-20250424185541483](./hpc-2025-heterogeneous-system/image-20250424185541483.webp) + +### Final Stage: Multi GPU Board + +![image-20250424190159059](./hpc-2025-heterogeneous-system/image-20250424190159059.webp) diff --git a/source/posts/hpc-2025-heterogeneous-system/image-20250417195644624.webp b/source/posts/hpc-2025-heterogeneous-system/image-20250417195644624.webp new file mode 100644 index 0000000..88ea13c --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system/image-20250417195644624.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c900e50ff87390ebda85f9d67e3a38ca4cf44af8e05738bdab048a01eb9922a +size 20734 diff --git a/source/posts/hpc-2025-heterogeneous-system/image-20250417200241703.webp b/source/posts/hpc-2025-heterogeneous-system/image-20250417200241703.webp new file mode 100644 index 0000000..0caecc5 --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system/image-20250417200241703.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0dae80ace7bc2918887f3c343ce2642c1ef98197a02d66ee679f767a769ef242 +size 49858 diff --git a/source/posts/hpc-2025-heterogeneous-system/image-20250424184701573.webp b/source/posts/hpc-2025-heterogeneous-system/image-20250424184701573.webp new file mode 100644 index 0000000..4ab29af --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system/image-20250424184701573.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c674ca7e69cbcf22fad70f3d50eb74ac00d087f3963f0efcf989d33057877670 +size 55266 diff --git a/source/posts/hpc-2025-heterogeneous-system/image-20250424185022360.webp b/source/posts/hpc-2025-heterogeneous-system/image-20250424185022360.webp new file mode 100644 index 0000000..01e51f2 --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system/image-20250424185022360.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:facf46bba579b084af9c30bbbfa67ca4861091179b4dcdac625140eba063dece +size 13764 diff --git a/source/posts/hpc-2025-heterogeneous-system/image-20250424185048036.webp b/source/posts/hpc-2025-heterogeneous-system/image-20250424185048036.webp new file mode 100644 index 0000000..0b76a77 --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system/image-20250424185048036.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:464e7414f5a4dc821561b985aab944e633dd4dab693fc6c75da245c5a969b7c6 +size 11604 diff --git a/source/posts/hpc-2025-heterogeneous-system/image-20250424185152081.webp b/source/posts/hpc-2025-heterogeneous-system/image-20250424185152081.webp new file mode 100644 index 0000000..99e5944 --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system/image-20250424185152081.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c8fb5d6381ab77461c0e8ea2fcc25887d1e80d8034687cc98b91a93bee0d03e +size 8946 diff --git a/source/posts/hpc-2025-heterogeneous-system/image-20250424185219673.webp b/source/posts/hpc-2025-heterogeneous-system/image-20250424185219673.webp new file mode 100644 index 0000000..771fa6b --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system/image-20250424185219673.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8466f02dbf307374fd2b16d3ceef32759af45016808af2d06347be05a6aed9c2 +size 11440 diff --git a/source/posts/hpc-2025-heterogeneous-system/image-20250424185322963.webp b/source/posts/hpc-2025-heterogeneous-system/image-20250424185322963.webp new file mode 100644 index 0000000..43577d0 --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system/image-20250424185322963.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1ab29cfe58e3cb5137c5e7d7fe167f82a5a3c9331cbb29019b6614a5ae2dffc +size 14300 diff --git a/source/posts/hpc-2025-heterogeneous-system/image-20250424185354247.webp b/source/posts/hpc-2025-heterogeneous-system/image-20250424185354247.webp new file mode 100644 index 0000000..4e97e9a --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system/image-20250424185354247.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:180ce40db7a628338e349c5443133b5448d8e90c676b0a6cdc60e7ec49ed4922 +size 11668 diff --git a/source/posts/hpc-2025-heterogeneous-system/image-20250424185449577.webp b/source/posts/hpc-2025-heterogeneous-system/image-20250424185449577.webp new file mode 100644 index 0000000..9e05280 --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system/image-20250424185449577.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8aa7937e9ecc5eefda100ab6eef58bbb02aa34e89d551d7d6113732fb93e2db3 +size 7570 diff --git a/source/posts/hpc-2025-heterogeneous-system/image-20250424185541483.webp b/source/posts/hpc-2025-heterogeneous-system/image-20250424185541483.webp new file mode 100644 index 0000000..54a0fdb --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system/image-20250424185541483.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1203d511a25cc66168356ff8356dc0d5527651b6f38acd2124e1284408e3277 +size 10742 diff --git a/source/posts/hpc-2025-heterogeneous-system/image-20250424190159059.webp b/source/posts/hpc-2025-heterogeneous-system/image-20250424190159059.webp new file mode 100644 index 0000000..2118571 --- /dev/null +++ b/source/posts/hpc-2025-heterogeneous-system/image-20250424190159059.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f847c355799225bdf76a8a6c52bcbff06d937042ea3de1a41fd31f4ad10b43dd +size 17618 diff --git a/source/posts/hpc-2025-intro.md b/source/posts/hpc-2025-intro.md new file mode 100644 index 0000000..c539db5 --- /dev/null +++ b/source/posts/hpc-2025-intro.md @@ -0,0 +1,33 @@ +--- +title: High Performance Computing 25 SP Introduction +date: 2025-03-08T00:31:54.5775860+08:00 +tags: +- 高性能计算 +- 学习资料 +--- + +High performance computing is the use of supercomputing and computing clusters to solve advanced problems. + + + +> High Performance Computing: Software execution speedup. +> +> High Throughput Computing: Resource utilization efficiency. + +High performance computing: + +- Provide computer power. +- Full-Domain HPC = Full-Stack HPC + Full-Network HPC. + +Two key weapons: **partitioning** and **duplicating** to increase granularity. + +HPC History: + +- Mainframe computer +- Mini computer +- Cluster +- Grids +- Clouds + + + diff --git a/source/posts/hpc-2025-non-stored-program-computing.md b/source/posts/hpc-2025-non-stored-program-computing.md new file mode 100644 index 0000000..e30a84c --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing.md @@ -0,0 +1,241 @@ +--- +title: High Performance Computing 25 SP Non Stored Program Computing +date: 2025-08-31T13:51:17.5260660+08:00 +tags: +- 高性能计算 +- 学习资料 +--- + + + +No Von Neumann Machines. + + + +## Application Specified Integrated Circuits + +As known as **ASIC**, these hardwares can work along and are not von Neumann machines. + +No stored program concept: + +- Input data come in +- Pass through all circuit gates quickly +- Generate output results immediately + +Advantages: performance is better. + +Disadvantages: reusability is worse. + +> The CPU and GPU are special kinds of ASIC. + +Why we need ASIC in computing: + +- Alternatives to the Moore'a law. +- High capacity and high speed. + +![image-20250605185212740](./hpc-2025-non-stored-program-computing/image-20250605185212740.webp) + +### Full Custom ASICs + +All mask layers are customized in a full-custom ASICs. + +The full-custom ASICs always can offer the highest performance and lowest part cost (smallest die size) for a given design. + +A typical example of full-custom ASICs is the CPU. + +The advantages and disadvantages of full-custom ASICs is shown below. + +| Advantages | Disadvantages | +| ------------------------------------------------------------ | -------------------------------------------------------- | +| Reducing the area | The design process takes a longer time | +| Enhancing the performance | Having more complexity in computer-aided design tool | +| Better ability of integrating with other analog components and other pre-designed components | Requiring higher investment and skilled human resources. | + +### Semi Custom ASICs + +All the logical cell are predesigned and some or all of the mask layer is customized. + +There are two types of semi-custom ASICs: + +- Standard cell based ASICs +- Gate-array based ASICs. + +The Standard cell based ASICs is also called as **Cell-based ASIC(CBIC)**. + +![image-20250815093113115](./hpc-2025-non-stored-program-computing/image-20250815093113115.webp) + +> The *gate* is used a unit to measure the ability of semiconductor to store logical elements. + + The semi-custom ASICs is developed as: + +- Programmable Logic Array(PLA) +- Complex Programmable Logical Device(CPLD) +- Programmable Array Logical +- Field Programing Gate Array(FPGA) + +#### Programmable Logical Device + +An integrated circuit that can be programmed/reprogrammed with a digital logical of a curtain level. + +The basic idea of PLD is an array of **AND** gates and an array of **OR** gates. Each input feeds both a non-inverting buffer and an inverting buffer to produce the true and inverted forms of each variable. The AND outputs are called the product lines. Each product line is connected to one of the inputs of each OR gate. + +Depending on the structure, the standard PLD can be divided into: + +- Read Only Memory(ROM): A fixed array of AND gates and a programmable array of OR gates. +- Programmable Array Logic(PAL): A programmable array of AND gates feeding a fixed array of OR gates. +- Programmable Logic Array(PLA): A programmable array of AND gates feeding a programmable of OR gates. +- Complex Programmable Logic Device(CPLD) and Field Programmable Gate Array(FPGA): complex enough to be called as *architecture*. + +![image-20250817183832472](./hpc-2025-non-stored-program-computing/image-20250817183832472.webp) + + + +## Field Programming Gate Array + +> General speaking, all semiconductor can be considered as a special kind of ASIC. But in practice, we always refer the circuit with a special function as ASIC, a circuit that can change the function as FPGA. + +![image-20250612184120333](./hpc-2025-non-stored-program-computing/image-20250612184120333.webp) + +### FPGA Architecture + +![image-20250817184419856](./hpc-2025-non-stored-program-computing/image-20250817184419856.webp) + +#### Configurable Logic Block(CLB) Architecture + +The CLB consists of: + +- Look-up Table(LUT): implements the entries of a logic functions truth table. + + And some FPGAs can use the LUTs to implement small random access memory(RAM). + +- Carry and Control Logic: Implements fast arithmetic operation(adders/subtractors). + +- Memory Elements: configures flip flops/latches (programmable clock edges, set, reset and clock enable). These memory elements usually can be configured as shift-registers. + +##### Configuring LUTs + +LUT is a ram with data width of 1 bit and the content is programmed at power up. Internal signals connect to control signals of MUXs to select a values of the truth tables for any given input signals. + +The below figure shows LUT working: + +![image-20250817185111521](./hpc-2025-non-stored-program-computing/image-20250817185111521.webp) + +The configuration memory holds the output of truth table entries, so that when the FPGA is restarting it will run with the same *program*. + +And as the truth table entries are just bits, the program of FPGA is called as **BITSTREAM**, we download a bitstream to an FPGA and all LUTs will be configured using the BITSTREAM to implement the boolean logic. + +##### LUT Based Ram + +Let the input signal as address, the LUT will be configured as a RAM. Normally, LUT mode performs read operations, the address decoders can generate clock signal to latches for writing operation. + +![image-20250817185859510](./hpc-2025-non-stored-program-computing/image-20250817185859510.webp) + +#### Routing Architecture + +The logic blocks are connected to each though programmable routing network. And the routing network provides routing connections among logic blocks and I/O blocks to complete a user-designed circuit. + +Horizontal and vertical mesh or wire segments interconnection by programmable switches called programmable interconnect points(PIPs). + +![image-20250817192006784](./hpc-2025-non-stored-program-computing/image-20250817192006784.webp) + +These PIPs are implemented using a transmission gate controlled by a memory bits from the configuration memory. + +Several types of PIPs are used in the FPGA: + +- Cross-point: connects vertical or horizontal wire segments allowing turns. +- Breakpoint: connects or isolates 2 wire segments. +- Decoded MUX: groups of cross-points connected to a single output configured by n configuration bits. +- Non-decoded MUX: n wire segments each with a configuration bit. +- Compound cross-point: 6 breakpoint PIPs and can isolate two isolated signal nets. + +![image-20250817194355228](./hpc-2025-non-stored-program-computing/image-20250817194355228.webp) + +#### Input/Output Architecture + +The I/O pad and surrounding supporting logical and circuitry are referred as input/input cell. + +The programmable Input/Output cells consists of three parts: + +- Bi-directional buffers +- Routing resources. +- Programmable I/O voltage and current levels. + +![image-20250817195139631](./hpc-2025-non-stored-program-computing/image-20250817195139631.webp) + +#### Fine-grained and Coarse-grained Architecture + +The fine-grained architecture: + +- Each logic block can implement a very simple function. +- Very efficient in implementing systolic algorithms. +- Has a large number of interconnects per logic block than the functionality they offer. + +The coarse-grained architecture: + +- Each logic block is relatively packed with more logic. +- Has their logic blocks packed with more functionality. +- Has fewer interconnections which leading to reduce the propagating delays encountered. + +#### Interconnect Devices + +FPGAs are based on an array of logic modules and uncommitted wires to route signal. + +Three types of interconnected devices have been commonly used to connect there wires: + +- Static random access memory (SRAM) based +- Anti-fuse based +- EEPROM based + +### FPGA Design Flow + +![image-20250817195714935](./hpc-2025-non-stored-program-computing/image-20250817195714935.webp) + +![image-20250817200350750](./hpc-2025-non-stored-program-computing/image-20250817200350750.webp) + +The FPGA configuration techniques contains: + +- Full configuration and read back. +- Partial re-configuration and read back. +- Compressed configuration. + +Based on the partially reconfiguration, the runtime reconfiguration is development. The area to be reconfigured is changed based on run-time. + +#### Hardware Description Languages(HDL) + +There are three languages targeting FPGAs: + +- VHDL: VHSIC Hardware Description Language. +- Verilog +- OpenCL + +The first two language are typical HDL: + +| Verilog | VHDL | +| -------------------------------------- | ------------------------------- | +| Has fixed data types. | Has abstract data types. | +| Relatively easy to learn. | Relatively difficult to learn. | +| Good gate level timing. | Poor level gate timing. | +| Interpreted constructs. | Compiled constructs. | +| Limited design reusability. | Good design reusability. | +| Doesn't support structure replication. | Supports structure replication. | +| Limited design management. | Good design management. | + +The OpenCL is not an traditional hardare description language. And OpenCL needs to turn the thread parallelism into hardware parallelism, called **pipeline parallelism**. + +The follow figure shows how the OpenCL-FPGA compiler turns an vector adding function into the circuit. + +![image-20250829210329225](./hpc-2025-non-stored-program-computing/image-20250829210329225.webp) + +The compiler generates three stages for this function: + +1. In the first stage, two loading units are used. +2. In the second stage, one adding unit is used. +3. In the third stage, one storing unit is used. + +Once cycle, the thread `N` is clocked in the first stage, loading values from the array meanwhile, the thread `N - 1` is in the second stage, adding values from the array and the thread `N - 2` is in the third stage, storing value into the target array. + +So different from the CPU and GPU, the OpenCL on the FPGA has two levels of parallelism: + +- Pipelining +- Replication of the kernels and having them run concurrently. + diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250605185212740.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250605185212740.webp new file mode 100644 index 0000000..f0aa943 --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250605185212740.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4fd2c98d02423204fd81464f246cf444d16c4cf26d0f0e16c1f7ab21c20ed9f +size 21240 diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250612184120333.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250612184120333.webp new file mode 100644 index 0000000..d2478c1 --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250612184120333.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13292910d38e288fc4d48b9d88326629702e7a653d2d1aa735687dd73efcc58f +size 30194 diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250815093113115.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250815093113115.webp new file mode 100644 index 0000000..e6096da --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250815093113115.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:319b14c97406e1136c65698092690ea5aaaf6c0daaffc3396b0c41c3503da48b +size 24062 diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250817183832472.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250817183832472.webp new file mode 100644 index 0000000..6fd531a --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250817183832472.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:132348b505996e94cb7e3a1ab7fc388a08d64935665a2d073c4dec49d813f88e +size 30534 diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250817184419856.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250817184419856.webp new file mode 100644 index 0000000..64b25a2 --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250817184419856.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8241a93f41e2919248d94df2a14f339e51c2b198eb0ac64129bc9e2def0fadc +size 62198 diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250817185111521.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250817185111521.webp new file mode 100644 index 0000000..513897c --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250817185111521.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e823c7aec0f9eed9f782707333e60370e8672c9b061f0c07d1f34c9dbaf4fbfa +size 41530 diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250817185859510.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250817185859510.webp new file mode 100644 index 0000000..8dc8b10 --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250817185859510.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ca33f447039cc7542bd184df73956852f751fce229e01706f97bf4e2ec65f14 +size 46126 diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250817192006784.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250817192006784.webp new file mode 100644 index 0000000..bcdcd1d --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250817192006784.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b283d276e446af57556649391c2cf086c3505f0c06dc13776e4753565c80ff1 +size 6702 diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250817194355228.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250817194355228.webp new file mode 100644 index 0000000..3672414 --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250817194355228.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:918e8fa4b3b016a139a4f37582f8b746dfb24957c273c3b03d0cc6af53b5c489 +size 29318 diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250817195139631.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250817195139631.webp new file mode 100644 index 0000000..184753b --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250817195139631.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08d76fc1e32f6be770a488954d603c2569c0da3d9e24707d24542bce84273efe +size 43880 diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250817195714935.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250817195714935.webp new file mode 100644 index 0000000..240b423 --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250817195714935.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d89201be9c596d39611487cc9a959355ebd9e8d899c343aea7d3ac83f3041013 +size 22906 diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250817200350750.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250817200350750.webp new file mode 100644 index 0000000..f3d4cbf --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250817200350750.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd110fe95490a1a8b59bf739a8502039268ba48eaf9e03c83f8b5174903d0fc3 +size 62148 diff --git a/source/posts/hpc-2025-non-stored-program-computing/image-20250829210329225.webp b/source/posts/hpc-2025-non-stored-program-computing/image-20250829210329225.webp new file mode 100644 index 0000000..a008953 --- /dev/null +++ b/source/posts/hpc-2025-non-stored-program-computing/image-20250829210329225.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9600d963770da35ec737ec963ec300143a8ee160ddadc29500f4192712e1a97 +size 35924 diff --git a/source/posts/hpc-2025-opencl.md b/source/posts/hpc-2025-opencl.md new file mode 100644 index 0000000..b87a0f9 --- /dev/null +++ b/source/posts/hpc-2025-opencl.md @@ -0,0 +1,100 @@ +--- +title: High Performance Computing 25 SP OpenCL Programming +date: 2025-08-31T13:51:02.0181970+08:00 +tags: +- 高性能计算 +- 学习资料 +--- + + +Open Computing Language. + + + +OpenCL is Open Computing Language. + +- Open, royalty-free standard C-language extension. +- For parallel programming of heterogeneous systems using GPUs, CPUs , CBE, DSP and other processors including embedded mobile devices. +- Managed by Khronos Group. + +![image-20250529185915068](./hpc-2025-opencl/image-20250529185915068.webp) + +### Anatomy of OpenCL + +- Platform Layer APi +- Runtime Api +- Language Specification + +### Compilation Model + +OpenCL uses dynamic/runtime compilation model like OpenGL. + +1. The code is compiled to an IR. +2. The IR is compiled to a machine code for execution. + +And in dynamic compilation, *step 1* is done usually once and the IR is stored. The app loads the IR and performs *step 2* during the app runtime. + +### Execution Model + +OpenCL program is divided into + +- Kernel: basic unit of executable code. +- Host: collection of compute kernels and internal functions. + +The host program invokes a kernel over an index space called an **NDRange**. + +NDRange is *N-Dimensional Range*, and can be a 1, 2, 3-dimensional space. + +A single kernel instance at a point of this index space is called **work item**. Work items are further grouped into **work groups**. + +### OpenCL Memory Model + +![image-20250529191215424](./hpc-2025-opencl/image-20250529191215424.webp) + +Multiple distinct address spaces: Address can be collapsed depending on the device's memory subsystem. + +Address space: + +- Private: private to a work item. +- Local: local to a work group. +- Global: accessible by all work items in all work groups. +- Constant: read only global memory. + +> Comparison with CUDA: +> +> ![image-20250529191414250](./hpc-2025-opencl/image-20250529191414250.webp) + +Memory region for host and kernel: + +![image-20250529191512490](./hpc-2025-opencl/image-20250529191512490.webp) + +### Programming Model + +#### Data Parallel Programming Model + +1. Define N-Dimensional computation domain +2. Work-items can be grouped together as *work group*. +3. Execute multiple work-groups in parallel. + +#### Task Parallel Programming Model + +> Data parallel execution model must be implemented by all OpenCL computing devices, but task parallel programming is a choice for vendor. + +Some computing devices such as CPUs can also execute task-parallel computing kernels. + +- Executes as s single work item. +- A computing kernel written in OpenCL. +- A native function. + +### OpenCL Framework + +![image-20250529192022613](./hpc-2025-opencl/image-20250529192022613.webp) + +The basic OpenCL program structure: + +![image-20250529192056388](./hpc-2025-opencl/image-20250529192056388.webp) + +**Contexts** are used to contain the manage the state of the *world*. + +**Command-queue** coordinates execution of the kernels. + diff --git a/source/posts/hpc-2025-opencl/image-20250529185915068.webp b/source/posts/hpc-2025-opencl/image-20250529185915068.webp new file mode 100644 index 0000000..dc2df0b --- /dev/null +++ b/source/posts/hpc-2025-opencl/image-20250529185915068.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d62a7e597cdc80ca23a85623c9ba3634d17bdc7b098a78e71860653e5d0e370 +size 21306 diff --git a/source/posts/hpc-2025-opencl/image-20250529191215424.webp b/source/posts/hpc-2025-opencl/image-20250529191215424.webp new file mode 100644 index 0000000..f0f66ad --- /dev/null +++ b/source/posts/hpc-2025-opencl/image-20250529191215424.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:621b4991c6cdcd881e92c43c919aaefcd6f7f5c0c3b97f7dc12e4e88a17128fa +size 22990 diff --git a/source/posts/hpc-2025-opencl/image-20250529191414250.webp b/source/posts/hpc-2025-opencl/image-20250529191414250.webp new file mode 100644 index 0000000..f02a710 --- /dev/null +++ b/source/posts/hpc-2025-opencl/image-20250529191414250.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7cf803398b2e38f84f4f1f7dd9dba6993d04e78f15b66688c0f1933e7c750394 +size 38110 diff --git a/source/posts/hpc-2025-opencl/image-20250529191512490.webp b/source/posts/hpc-2025-opencl/image-20250529191512490.webp new file mode 100644 index 0000000..bc0dda0 --- /dev/null +++ b/source/posts/hpc-2025-opencl/image-20250529191512490.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f7e34696ea72527d3777e96421a86167a9aec2b67aee55e381286994b977d1e +size 54722 diff --git a/source/posts/hpc-2025-opencl/image-20250529192022613.webp b/source/posts/hpc-2025-opencl/image-20250529192022613.webp new file mode 100644 index 0000000..2f16196 --- /dev/null +++ b/source/posts/hpc-2025-opencl/image-20250529192022613.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62ddaa56863cb8ce5639252c238b18109b273c5bda0cf8d036e23875037a906a +size 31330 diff --git a/source/posts/hpc-2025-opencl/image-20250529192056388.webp b/source/posts/hpc-2025-opencl/image-20250529192056388.webp new file mode 100644 index 0000000..45d0f9e --- /dev/null +++ b/source/posts/hpc-2025-opencl/image-20250529192056388.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1072d33cce9d615be20bd7010c93b772472ef2d50da1e4406107b4e9dabeb031 +size 19236 diff --git a/source/posts/hpc-2025-parallel-computing.md b/source/posts/hpc-2025-parallel-computing.md new file mode 100644 index 0000000..d24b419 --- /dev/null +++ b/source/posts/hpc-2025-parallel-computing.md @@ -0,0 +1,366 @@ +--- +title: High Performance Computing 25 SP Dichotomy of Parallel Computing Platforms +date: 2025-03-28T01:03:32.2187720+08:00 +tags: +- 高性能计算 +- 学习资料 +--- + + +Designing algorithms is always the hardest. + + + +Flynn's classical taxonomy: + +- SISD +- SIMD +- MISD +- MIMD + +Multiple instruction and multiple data is currently the most common type of parallel computer. + +> A variant: single program multiple data(SPMD). + +## Dichotomy of Parallel Computing Platforms + +Based on the logical and physical organization of parallel platforms. + +Logical organization (from a programmer's perspective): + +- Control structure: ways of expressing parallel tasks. +- Communication model: interactions between tasks. + +Hardware organization: + +- Architecture +- Interconnection networks. + +Control Structure of Parallel Platform: parallel tasks can be specified at various levels of granularity. + +Communication Model: **Shared address space platforms**. Support a common data space that is accessible to all processors. Two types of architectures: + +- Uniform memory access (UMA) +- Non-uniform memory access(NUMA) + +> NUMA and UMA are defined in term of memory access times not the cache access times. + +![image-20250313193604905](./hpc-2025-parallel-computing/image-20250313193604905.webp) + +NUMA and UMA: + +- The distinction between NUMA and UMA platforms is important from the point of view of algorithm design. + +- Programming these platforms is easier since reading and writing are implicitly visible to other processors. + +- Caches is such machines requires coordinated access to multiple copies. + + > Leads to cache coherence problem. + +- A weaker model of these machines provides an address map but not coordinated access. + +**Global Memory Space**: + +- Easy to program. + +- Read-only interactions: + + Invisible to programmers. + + Same as in serial programs. + +- Read/write interactions: + + Mutual exclusion for concurrent access such as lock and related mechanisms. + +- Programming paradigms: Threads/Directives. + +Caches in shared-address-space: + +- Address translation mechanism to locate a memory word in the system. +- Well-defined semantics over multiple copies(**cache coherence**). + +> Shared-address-space vs shared memory machine: +> +> Shared address space is a programming abstraction. +> +> Shared memory machine is a physical machine attribute. + +Distributed Shared Memory(DSM) or Shared Virtual Memory(SVM): + +- Page-based access control: leverage the virtual memory support and manage main memory as a fully associative cache on the virtual address space by embedding a coherence protocol in the page fault handler. +- Object based access control: flexible but no false sharing. + +## Parallel Algorithm Design + +Steps in parallel algorithm design: + +- Identifying portions of the work that can be performed concurrently. +- Mapping the concurrent pieces of work onto multiple processors running in parallel. +- Distributing the input, output and intermediate data associated with the program. +- Managing accesses to data shared by multiple processors. +- Synchronizing the processors at various stages of the parallel program execution. + +### Decomposition + +Dividing a computation into smaller parts some or all of which may be executed in parallel. + +Tasks: programmer-defined units with arbitrary size and is indivisible. + +Aim: **reducing execution time** + +Ideal decomposition: + +- All tasks have similar size. +- Tasks are **not** waiting for each other **not** sharing resources. + +Dependency graphs: + +Task dependency graph: an abstraction to express dependencies among tasks and their relative order of execution. + +- Directed acyclic graphs. +- Nodes are tasks. +- Directed edges: dependencies amongst tasks. + +> The fewer directed edges, the better as parallelism. + +Granularity: + +The granularity of the decomposition: the number and size of tasks into which a problem is decomposed. + +- Fine-grained: a large number of small tasks. +- Coarse-grained: a small number of large tasks. + +Concurrency: + +**maximum degree of concurrency** + +**Average degree of concurrency** + +The critical path determines the average degree of concurrency. + +Critical path is the longest directed path between any pair of start and finish nodes. So a shorter critical path favors a higher degree of concurrency. + +**Limited Granularity**: + +It may appear that increasing the granularity of decomposition will utilize the resulting concurrency. + +But there is a inherent bound on how fine-grained a decomposition a problem permits. + +Speedup: + +The ratio of serial to parallel execution time. Restrictions on obtaining unbounded speedup from: + +- Limited granularity. +- Degree of concurrency. +- Interaction among tasks running on different physical processors. + +Processor: + +Computing agent that performs tasks, an abstract entity that uses the code and data of a tasks to produce the output of the task within a finite amount of time. + +Mapping: the mechanism by which tasks are assigned to processor for execution. The task dependency and task interaction graphs play an important role. + +Decomposition techniques: + +Fundamental steps: split the computations to be performed into a set of tasks for concurrent execution. + +1. Recursive decomposition. + + A method for inducing concurrency in problems that can be solved using the **divide-and-conquer** strategy. + +2. Data decomposition. + + A method for deriving concurrency in algorithms that operate on large data structures. + + The operations performed by these tasks on different data partitions. + + Can be partitioning output data and partitioning input data or even partitioning intermediate data. + +3. Exploratory decomposition. + + Decompose problems whose underlying computations correspond to a search of a space for solutions. + + Exploratory decomposition appears similar to data decomposition. + +4. Speculative decomposition. + + Used when a program may take one of many possible computationally significant branches depending on the output of preceding computation. + + Similar to evaluating branches in a *switch* statement in `C` as evaluate multiple branches in parallel and correct branch will be used and other branches will be discarded. + + The parallel run time is smaller than the serial run time by the amount of time to evaluate the condition. + +### Characteristics of Tasks + +**Task generation**: + +- Static: all the tasks are known before the algorithm starts executing. +- Dynamic: the actual tasks and the task dependency graph are not explicitly available at priori. +- Either static or dynamic. + +**Task Sizes**: + +The relative amount of time required t complete the task. + +- Uniform +- Non-uniform + +The knowledge of task sizes will influence the choice of mapping scheme. + +**Inter-Task Interactions**: + +- Static versus dynamic. +- Regular versus irregular. +- Read-only versus read-write +- One-way versus two-way. + +### Mapping Techniques + +Mapping techniques is for loading balancing. + +Good mappings: + +- Reduce the interaction time. +- Reduce the idle time. + +![image-20250320200524155](./hpc-2025-parallel-computing/image-20250320200524155.webp) + +There are two mapping methods: + +- **Static Mapping**: determined by programming paradigm and the characteristics of tasks and interactions. + + Static mapping is often used in conjunction with *data partitioning* and *task partitioning*. + +- **Dynamic Mapping**: distribute the work among processors during the execution. Also referred as dynamic load-balancing. + + The **centralized scheme** as all the executable tasks are maintained in a common central data structure and distributed by a special process or a subset of processes as **master** process. + + Centralized scheme always means easy to implement but with limited scalability. + + The **distributed scheme** as the set of executable tasks are distributed among processes which exchange tasks at run time to balance work. + +**Minimize frequency of interactions**: + +There is a relatively high startup cost associated with each interaction on many architectures. + +So restructure the algorithm such that shared data are accessed and used in large pieces. + +**Minimize contention and hot spots**: + +Contention occurs when multiple tasks try to access the same resources concurrently. + +And centralized scheme for dynamic mapping are a frequent source of contention so use the distributed mapping schemes. + +**Overlapping computations with interactions**: + +When waiting for shared data, do some useful computations. + +- Initiate an interaction early enough to complete before it needed. +- In dynamic mapping schemes, the process can anticipate that it is going to run out of work and initiate a work which transfers interaction in advance. + +Overlapping computations with interaction requires support from the programming paradigm, the operating system and the hardware. + +- Disjoint address-space paradigm: non-blocking message passing primitives. +- Share address-space paradigm: prefetching hardware which can anticipate the memory addresses and initiate access in advance of when they are needed. + +**Replicating data or computations**: + +Multiple processors may require frequent read-only access to shared data structure such as a hash-table. + +For different paradigm: + +- Share address space use cache. +- Message passing: remote data accesses are more expensive and harder than local accesses. + +Data replication increases the memory requirements. In some situation, it may be more cost-effective to compute these intermediate results than to get then from another place. + +**Using optimized collective interaction operations**: + +Collective operations are like: + +- Broadcasting some data to all processes. +- Adding up numbers each belonging to a different process. + +### Parallel Algorithm Model + +The way of structuring parallel algorithm by + +- Selecting a decomposition +- Selecting a mapping technique. +- Applying the appropriate strategy to minimize interactions. + +**Data parallel model**: + +The tasks are statically or semi-statically mapped onto processes and each task performs similar operations on different data. + +Example: matrix multiplication. + +**Task graph model**: + +The interrelations among the tasks are utilized to promote locality or to reduce interaction costs. + +Example: quick sort, sparse matrix factorization and many other algorithms using divide-and-conquer decomposition. + +**Work pool model**: + +Characterized by a dynamic mapping of task onto processes for load balancing. + +Example: parallelization of loops by chunk scheduling. + +**Master-slave model** : + +One or more master processes generate work and allocate it to worker processes. + +**Pipeline or producer-consumer model**: + +A stream of data is passed on through a succession of processes, each of which performs some tasks. + +### Analytical Modeling of Parallel Programs + +**Performance evaluation**: + +Evaluation in terms of execution time. + +A parallel system is the combination of an algorithm and the parallel architecture on which it is implemented. + +**Sources of overhead in parallel program**: + +A typical execution includes: + +- Essential computation + + Computation that world be performed by the serial program for solving the same problem instance. + +- Interprocess communication + +- Idling + +- Excess computation + + Computation which not performed by the serial program. + +**Performance metrics for parallel system**: + +- Execution time +- Overhead function +- Total overhead +- Speedup + +> For a given problem, more than one sequential algorithm may be available. + +Theoretically speaking, speed up can never exceed the number of PE. + +If super linear speedup: the work performed by a serial program is greater than its parallel formulation, maybe hardware features that put the serial implementation at a disadvantage. + +**Amdahl's Law**: + +![image-20250327194045418](./hpc-2025-parallel-computing/image-20250327194045418.webp) + +The overall performance improvement gained by optimizing a single part of a system is limited by the fraction of time that the improved part is actually used. + +Efficiency: a measure of the fraction of time for which a PE is usefully employed. + +Cost: the product of parallel run time and the number of processing elements used. + +![image-20250327194312962](./hpc-2025-parallel-computing/image-20250327194312962.webp) diff --git a/source/posts/hpc-2025-parallel-computing/image-20250313193604905.webp b/source/posts/hpc-2025-parallel-computing/image-20250313193604905.webp new file mode 100644 index 0000000..7583add --- /dev/null +++ b/source/posts/hpc-2025-parallel-computing/image-20250313193604905.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4cee39cf06e317dc23379d237149a3022645d48074421e011ee0962867733fc7 +size 21450 diff --git a/source/posts/hpc-2025-parallel-computing/image-20250320200524155.webp b/source/posts/hpc-2025-parallel-computing/image-20250320200524155.webp new file mode 100644 index 0000000..6461154 --- /dev/null +++ b/source/posts/hpc-2025-parallel-computing/image-20250320200524155.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcb8c580e257a5082ff13ba56a5ebb566e3d3f8d2d5b6e5cc30a303fe638c052 +size 18076 diff --git a/source/posts/hpc-2025-parallel-computing/image-20250327194045418.webp b/source/posts/hpc-2025-parallel-computing/image-20250327194045418.webp new file mode 100644 index 0000000..c373f23 --- /dev/null +++ b/source/posts/hpc-2025-parallel-computing/image-20250327194045418.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d29035641271b3afdb031440da7a492678dade7c013326cdec042dab1d80d5e7 +size 3788 diff --git a/source/posts/hpc-2025-parallel-computing/image-20250327194312962.webp b/source/posts/hpc-2025-parallel-computing/image-20250327194312962.webp new file mode 100644 index 0000000..bee965a --- /dev/null +++ b/source/posts/hpc-2025-parallel-computing/image-20250327194312962.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d880fa74f67c03334304803d036547b881f0f301a4495f96e2f783671b87b8e9 +size 24820 diff --git a/source/posts/hpc-2025-potpourri.md b/source/posts/hpc-2025-potpourri.md new file mode 100644 index 0000000..17974d2 --- /dev/null +++ b/source/posts/hpc-2025-potpourri.md @@ -0,0 +1,79 @@ +--- +title: High Performance Computing 25 SP Potpourri +date: 2025-08-31T13:51:29.8809980+08:00 +tags: +- 高性能计算 +- 学习资料 +--- + + +Potpourri has a good taste. + + + +## Heterogeneous System Architecture + +![image-20250612185019968](./hpc-2025-potpourri/image-20250612185019968.webp) + +The goals of the HSA: + +- Enable power efficient performance. +- Improve programmability of heterogeneous processors. +- Increase the portability of code across processors and platforms. +- Increase the pervasiveness of heterogeneous solutions. + +### The Runtime Stack + +![image-20250612185221643](./hpc-2025-potpourri/image-20250612185221643.webp) + +## Accelerated Processing Unit + +A processor that combines the CPU and the GPU elements into a single architecture. + +![image-20250612185743675](./hpc-2025-potpourri/image-20250612185743675.webp) + +## Intel Xeon Phi + +The goal: + +- Leverage X86 architecture and existing X86 programming models. +- Dedicate much of the silicon to floating point ops. +- Cache coherent. +- Increase floating-point throughput. +- Strip expensive features. + +The reality: + +- 10s of x86-based cores. +- Very high-bandwidth local GDDR5 memory. +- The card runs a modified embedded Linux. + +## Deep Learning: Deep Neural Networks + +The network can used as a computer. + +## Tensor Processing Unit + +A custom ASIC for the phase of Neural Networks (AI accelerator). + +### TPUv1 Architecture + +![image-20250612191035632](./hpc-2025-potpourri/image-20250612191035632.webp) + +### TPUv2 Architecture + +![image-20250612191118473](./hpc-2025-potpourri/image-20250612191118473.webp) + +Advantages of TPU: + +- Allows to make predications very quickly and respond within fraction of a second. +- Accelerate performance of linear computation, key of machine learning applications. +- Minimize the time to accuracy when you train large and complex network models. + +Disadvantages of TPU: + +- Linear algebra that requires heavy branching or are not computed on the basis of element wise algebra. +- Non-dominated matrix multiplication is not likely to perform well on TPUs. +- Workloads that access memory using sparse technique. +- Workloads that use highly precise arithmetic operations. + diff --git a/source/posts/hpc-2025-potpourri/image-20250612185019968.webp b/source/posts/hpc-2025-potpourri/image-20250612185019968.webp new file mode 100644 index 0000000..c4386d4 --- /dev/null +++ b/source/posts/hpc-2025-potpourri/image-20250612185019968.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d85a51185cf7b9cb9fb45e59f4024b38e04cc148046ad32a0a0bf44e347ac4f4 +size 22410 diff --git a/source/posts/hpc-2025-potpourri/image-20250612185221643.webp b/source/posts/hpc-2025-potpourri/image-20250612185221643.webp new file mode 100644 index 0000000..e8b18cd --- /dev/null +++ b/source/posts/hpc-2025-potpourri/image-20250612185221643.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cc99eda739d7546257bff4564c43320381c79d8c842ff239cf044394e065448 +size 16240 diff --git a/source/posts/hpc-2025-potpourri/image-20250612185743675.webp b/source/posts/hpc-2025-potpourri/image-20250612185743675.webp new file mode 100644 index 0000000..7233d59 --- /dev/null +++ b/source/posts/hpc-2025-potpourri/image-20250612185743675.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bfe9c597fda41f26b68a6ce09bb8e14fd303ee4194d83725ecdffed9402a8ab +size 77656 diff --git a/source/posts/hpc-2025-potpourri/image-20250612191035632.webp b/source/posts/hpc-2025-potpourri/image-20250612191035632.webp new file mode 100644 index 0000000..ebe8616 --- /dev/null +++ b/source/posts/hpc-2025-potpourri/image-20250612191035632.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ec5968a1677c98328ac58413baabcd5ff5352e7e9f09bae8ace8499fb369c57 +size 42574 diff --git a/source/posts/hpc-2025-potpourri/image-20250612191118473.webp b/source/posts/hpc-2025-potpourri/image-20250612191118473.webp new file mode 100644 index 0000000..8aaf734 --- /dev/null +++ b/source/posts/hpc-2025-potpourri/image-20250612191118473.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df7c663316c7df33aab6fbfe738b69d4f7bb6e4e61cf91a1896d650b926678ce +size 18820 diff --git a/source/posts/hpc-2025-program-cuda.md b/source/posts/hpc-2025-program-cuda.md new file mode 100644 index 0000000..e4ff6b7 --- /dev/null +++ b/source/posts/hpc-2025-program-cuda.md @@ -0,0 +1,42 @@ +--- +title: High Performance Computing 25 SP Programming CUDA +date: 2025-08-31T13:50:53.6891520+08:00 +tags: +- 高性能计算 +- 学习资料 +--- + + +Compute Unified Device Architecture + + + +## CUDA + +General purpose programming model: + +- Use kicks off batches of threads on the GPU. + +![image-20250515195739382](./hpc-2025-program-cuda/image-20250515195739382.webp) + +The compiling C with CUDA applications: + +![image-20250515195907764](./hpc-2025-program-cuda/image-20250515195907764.webp) + +### CUDA APIs + +Areas: + +- Device management +- Context management +- Memory management +- Code module management +- Execution control +- Texture reference management +- Interoperability with OpenGL and Direct3D + +Two APIs: + +- A low-level API called the CUDA driver API. +- A higher-level API called the C runtime for CUDA that is implemented on top of the CUDA driver API. + diff --git a/source/posts/hpc-2025-program-cuda/image-20250515195739382.webp b/source/posts/hpc-2025-program-cuda/image-20250515195739382.webp new file mode 100644 index 0000000..ff5eaa3 --- /dev/null +++ b/source/posts/hpc-2025-program-cuda/image-20250515195739382.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6262a7c1a9bd6e60c823a20149f1c3d5885ccf77ed09de432cd6406bb03efaef +size 27260 diff --git a/source/posts/hpc-2025-program-cuda/image-20250515195907764.webp b/source/posts/hpc-2025-program-cuda/image-20250515195907764.webp new file mode 100644 index 0000000..5af4243 --- /dev/null +++ b/source/posts/hpc-2025-program-cuda/image-20250515195907764.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67ae24b125253e6e2d475cd0ffbe5dd66ef98c13e4da8d309d770b4e2e1d9bad +size 29910 diff --git a/source/posts/hpc-2025-program-smp-platform.md b/source/posts/hpc-2025-program-smp-platform.md new file mode 100644 index 0000000..1a4c19e --- /dev/null +++ b/source/posts/hpc-2025-program-smp-platform.md @@ -0,0 +1,106 @@ +--- +title: High Performance Computing 25 SP Programming SMP Platform +date: 2025-05-10T00:17:26.5784020+08:00 +tags: +- 高性能计算 +- 学习资料 +--- + + + +Sharing address space brings simplification. + + + +### Shared Address Space Programming Paradigm + +Vary on mechanism for data sharing, concurrency models and support for synchronization. + +- Process based model +- Lightweight processes and threads. + +### Thread + +A thread is a single stream of control in the flow of a program. + +**Logical memory model of a thread**: + +All memory is globally accessible to every thread and threads are invoked as function calls. + +![image-20250327200344104](./hpc-2025-program-smp-platform/image-20250327200344104.webp) + +Benefits of threads: + +- Take less time to create a new thread than a new process. +- Less time to terminate a thread than a process. + +The taxonomy of thread: + +- User-Level Thread(ULT): The kernel is not aware of the existence of threads. All thread management is done by the application using a thread library. Thread switching does not require kernel mode privileges and scheduling is application specific. +- Kernel-Level Thread(KLT): All thread management is done by kernel. No thread library but an API to the kernel thread facility. Switching between threads requires the kernel and schedule on a thread basis. +- Combined ULT and KLT approaches. Combines the best of both approaches. + +![image-20250403183104279](./hpc-2025-program-smp-platform/image-20250403183104279.webp) + +## PThread Programming + +Potential problems with threads: + +- Conflicting access to shared memory. +- Race condition occur. +- Starvation +- Priority inversion +- Deadlock + +### Mutual Exclusion + +Mutex locks: implementing critical sections and atomic operations. + +Two states: locked and unlocked. At any point of time, only one thread can lock a mutex lock. + +### Producer-Consumer Work Queues + +The producer creates tasks and inserts them into a work-queue. + +The consumer threads pick up tasks from the task queue and execute them. + +Locks represent serialization points since critical sections must be executed by threads one after the other. + +**Important**: Minimize the size of critical sections. + +### Condition Variables for Synchronization + +The `pthread_mutex_trylock` alleviates the idling time but introduce the overhead of polling for availability of locks. + +An interrupt driven mechanism as opposed to a polled mechanism as the availability is signaled. + +A **condition variable**: a data object used for synchronizing threads. Block itself until specified data reaches a predefined state. + +When a thread performs a condition wait, it's not runnable as not use any CPU cycles but a mutex lock consumes CPU cycles as it polls for the locks. + +**Common Errors**: One cannot assume any order of execution, must be explicitly established by mutex, condition variables and joins. + +## MPI Programming + +Low cost message passing architecture. + +![image-20250403191254323](./hpc-2025-program-smp-platform/image-20250403191254323.webp) + +Mapping of MPI Processes: + +MPI views the processes as a one-dimensional topology. But in parallel programs, processes are arranged in higher-dimensional topologies. So it is required to map each MPI process to a process in the higher dimensional topology. + +Non-blocking Send and Receive: + +`MPI_ISend` and `MPI_Irecv` functions allocate a request object and return a pointer to it. + +## OpenMP + +A standard for directive based parallel programming. + +Thread based parallelism and explicit parallelism. + +Use fork-join model: + +![image-20250403195750934](./hpc-2025-program-smp-platform/image-20250403195750934.webp) + diff --git a/source/posts/hpc-2025-program-smp-platform/image-20250327200344104.webp b/source/posts/hpc-2025-program-smp-platform/image-20250327200344104.webp new file mode 100644 index 0000000..258b9c4 --- /dev/null +++ b/source/posts/hpc-2025-program-smp-platform/image-20250327200344104.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81a2610afcd3913bfeb523eb861d63680fa81b7a9741c1cf10c4ecca16081558 +size 25110 diff --git a/source/posts/hpc-2025-program-smp-platform/image-20250403183104279.webp b/source/posts/hpc-2025-program-smp-platform/image-20250403183104279.webp new file mode 100644 index 0000000..4519ed0 --- /dev/null +++ b/source/posts/hpc-2025-program-smp-platform/image-20250403183104279.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:305c571375a81ada7dbaf9e3c32afffecad3a9b2a60d7a148a94ecf93336555d +size 33248 diff --git a/source/posts/hpc-2025-program-smp-platform/image-20250403191254323.webp b/source/posts/hpc-2025-program-smp-platform/image-20250403191254323.webp new file mode 100644 index 0000000..67a253b --- /dev/null +++ b/source/posts/hpc-2025-program-smp-platform/image-20250403191254323.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:061682bb2fbc7c9eab831d262dc6419a1d8d58a0c40bc012be7d8a5a091c3bf2 +size 63130 diff --git a/source/posts/hpc-2025-program-smp-platform/image-20250403195750934.webp b/source/posts/hpc-2025-program-smp-platform/image-20250403195750934.webp new file mode 100644 index 0000000..48a67d5 --- /dev/null +++ b/source/posts/hpc-2025-program-smp-platform/image-20250403195750934.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:874702176e18c0e122750042b0985c52e2eccb0d3444323d3d4601318535937c +size 24408 diff --git a/YaeBlog/source/posts/install-pytorch.md b/source/posts/install-pytorch.md similarity index 98% rename from YaeBlog/source/posts/install-pytorch.md rename to source/posts/install-pytorch.md index 11f8314..04304a4 100644 --- a/YaeBlog/source/posts/install-pytorch.md +++ b/source/posts/install-pytorch.md @@ -1,12 +1,11 @@ --- title: 安装pytorch,来有深度的学习 -date: 2021-11-13 13:21:53 -toc: true -tags: - - 技术笔记 -typora-root-url: 安装pytorch,来有深度的学习 +date: 2021-11-13T13:21:53.0000000 +tags: +- 技术笔记 --- + 鄙人在下不才我精通深度学习框架的安装和卸载。 @@ -40,7 +39,7 @@ typora-root-url: 安装pytorch,来有深度的学习 我们访问`miniconda`的[官方网站](https://docs.conda.io/en/latest/miniconda.html) ,下滑找到最新的`miniconda`安装包下载链接(Latest Miniconda Installer Links)。 -![](1.png) +![](1.webp) 按照自己的电脑系统下载相应的安装包就可以了。也不大,就50M出头的样子。 @@ -48,7 +47,7 @@ typora-root-url: 安装pytorch,来有深度的学习 安装完成之后,我们就会发现自己的开始菜单里多出来了两个快捷方式, -![](2.png) +![](2.webp) 在上面的那个快捷方式**Anaconda Prompt**是运行含有`conda`命令的CMD的快捷方式,而下面那个**Anaconda Power Shell Prompt**是运行含有`conda`命令的Power shell界面的快捷方式。因为我个人对于power shell更加的熟悉我们就是用下面的那个快捷方式。 这个时候肯定会有人好奇,竟然你都用power shell了,我们为啥不直接使用系统里已经有了的power shell,而要启动这里的这个显得有点奇怪的power shell呢? @@ -57,7 +56,7 @@ typora-root-url: 安装pytorch,来有深度的学习 好,我们运行**Anaconda Power Shell Prompt**,打开了这个窗口 -![](3.png) +![](3.webp) 这个窗口的出现就标志着我们已经把`conda`正确的安装在我们的电脑之中了。 @@ -160,7 +159,7 @@ conda update python ### 使用conda安装pytorch 我们访问pytorch的[官方网站](https://pytorch.org/),我们可以轻松在首页就找到这个表格 -![](4.png) +![](4.webp) 先简单介绍一下这个表格的每一行,第一行是选择我们安装`pytorch`的版本,是稳定版,预览版,还是长期支持版。第二行是选择我们索要下载的操作系统,这里就默认大家都是用Windows了,第三行是选择我们安装`pytorch`的方式,可以看见有我们刚学习的`conda`,我们很熟悉的`pip`, 还有两种其他的方式。我们选择`conda`。第三行是我们所使用的语言,我们自然选择`python`。然后是第四行,“Compute Platform”, 如果直译的话是计算平台,就是我们选择用来计算的设备。这里就是又一个新的知识点了。一般来说,我们在使用电脑时,都是使用CPU作为计算的主力,显卡(GPU)一般只是用来输出图像,但在深度学习出现后,人们发现显卡原本专精于图形计算的计算力也很适用于深度学习中人工神经网络的计算。为了能够使用GPU的算力,而不是让它仅仅输出图像,我们就得下载相关的工具,这个工具就是CUDA。当然,这种比较强大的能力并不是只要下载一个CUDA就可以拥有的,~~你还得有钱~~你还得拥有一块英伟达的显卡,如果你没有显卡,那你就只能老老实实的使用自己的CPU进行计算了,在表格的第四行选择CPU。而对于我们~~土豪~~有显卡的同学,我们得先确定确定自己电脑上CUDA的版本,打开我们的老朋友power shell,输入 ``` diff --git a/source/posts/install-pytorch/1.webp b/source/posts/install-pytorch/1.webp new file mode 100644 index 0000000..b7c4f24 --- /dev/null +++ b/source/posts/install-pytorch/1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7399c6fd6cdf7689105607739c5b67432f6f723508b3db2dc5c370219074533f +size 30520 diff --git a/source/posts/install-pytorch/2.webp b/source/posts/install-pytorch/2.webp new file mode 100644 index 0000000..e8a8576 --- /dev/null +++ b/source/posts/install-pytorch/2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4a236af737ef2fc99e7447b6da73e5fe7bd4687823235535081592e6bdbfbaf +size 20334 diff --git a/source/posts/install-pytorch/3.webp b/source/posts/install-pytorch/3.webp new file mode 100644 index 0000000..c1ed6ff --- /dev/null +++ b/source/posts/install-pytorch/3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f36e3aeb862b586999a2f0a456da844c7472d3f86ef8dd7a562218bd2802698 +size 2242 diff --git a/source/posts/install-pytorch/4.webp b/source/posts/install-pytorch/4.webp new file mode 100644 index 0000000..365f7b2 --- /dev/null +++ b/source/posts/install-pytorch/4.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:037a17878e6494a6beca6e4fd4905b1a6ca025d6ccd2e3754a3806bfb114973f +size 15894 diff --git a/YaeBlog/source/posts/laptop-for-computer.md b/source/posts/laptop-for-computer.md similarity index 98% rename from YaeBlog/source/posts/laptop-for-computer.md rename to source/posts/laptop-for-computer.md index d5aaff2..39e33fa 100644 --- a/YaeBlog/source/posts/laptop-for-computer.md +++ b/source/posts/laptop-for-computer.md @@ -1,12 +1,12 @@ --- title: 大学生用啥配置——计算机专业 +date: 2022-06-13T16:17:27.0000000 tags: - - 随笔 -typora-root-url: laptop-for-computer -date: 2022-06-13 16:17:27 +- 杂谈 --- + > 本文是应B站UP主[远古时代装机猿](https://space.bilibili.com/35359510)发起的[大学生用啥配置](https://www.bilibili.com/video/BV1kZ4y1i7Le)公益活动而写 > > 目前某不知名211大学计算机专业在读 @@ -52,7 +52,7 @@ date: 2022-06-13 16:17:27 目前的性能占用如图所示 -![](c.png) +![](c.webp) 在编译代码时CPU占用会有一个短时的提升,不过由于目前编写的程序都较为简单,编译转瞬就完成了。 @@ -62,7 +62,7 @@ date: 2022-06-13 16:17:27 性能的占用如图所示 -![](clion.png) +![](clion.webp) 在刚打开项目和编译整个项目时CPU的占用会上升,但在完成之后,CPU的占用就会回落。 @@ -74,7 +74,7 @@ date: 2022-06-13 16:17:27 性能占用如图所示 -![](csharp.png) +![](csharp.webp) #### 写网站 @@ -82,7 +82,7 @@ date: 2022-06-13 16:17:27 性能占用如图所示: -![](web.png) +![](web.webp) ### 软件的运行需求分析 diff --git a/source/posts/laptop-for-computer/c.webp b/source/posts/laptop-for-computer/c.webp new file mode 100644 index 0000000..3ede867 --- /dev/null +++ b/source/posts/laptop-for-computer/c.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d93cb06f6bb8472848519ff59e336d47024159570100f13637b10b91e9dc7ba3 +size 103014 diff --git a/source/posts/laptop-for-computer/clion.webp b/source/posts/laptop-for-computer/clion.webp new file mode 100644 index 0000000..830f8cc --- /dev/null +++ b/source/posts/laptop-for-computer/clion.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f991d47571c3f8e4eb1d8abf35785fd112f0d82ee56c1993cefc8b530a797b5f +size 107082 diff --git a/source/posts/laptop-for-computer/csharp.webp b/source/posts/laptop-for-computer/csharp.webp new file mode 100644 index 0000000..c592760 --- /dev/null +++ b/source/posts/laptop-for-computer/csharp.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9887563cc3b3b76b2f5b305583ff0b6368277347a6f5afc250d46bc0624d42e0 +size 109156 diff --git a/source/posts/laptop-for-computer/web.webp b/source/posts/laptop-for-computer/web.webp new file mode 100644 index 0000000..f0ea85a --- /dev/null +++ b/source/posts/laptop-for-computer/web.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:786ab13b2c81724b80ce0aa82ecdf54aa5e469f538e1c0484d5f5038ed21d910 +size 105566 diff --git a/source/posts/linux-distribution-from-zero.md b/source/posts/linux-distribution-from-zero.md new file mode 100644 index 0000000..4a06941 --- /dev/null +++ b/source/posts/linux-distribution-from-zero.md @@ -0,0 +1,306 @@ +--- +title: 从零开始的Linux发行版生活 +date: 2025-05-27T14:22:45.9208348+08:00 +tags: +- Linux +- 技术笔记 +--- + + + 总有些时候我们需要自己组装Linux操作系统,比如交叉编译、嵌入式开发和可信执行环境开发等等场景。本文便介绍如何使用Arch Linux作为基础在`riscv`架构上组装操作系统并使用QEMU运行。 + + + +## 初始化根文件系统 + +`rootfs`是Linux系统中除了内核之外的其他文件的总和,例如`/usr`和`/etc`中重要的系统文件均属于`rootfs`的范围。在进行Linux系统的开发时,同一架构的`rootfs`之间基本上可以互换,例如可以把Arch Linux的`rootfs`替换到`ubuntu`系统中,而内核由于硬件的敏感性,通常需要使用特定厂商提供的内核(在更改合入upstream之前)。 + +> 实际上,除了各个发行版对于内核的修改,各个发行版之间主要的不同就是rootfs的不同。 + +首先创建一个`rootfs`文件夹并修改权限为`root`。 + +```bash +mkdir rootfs +sudo chown root:root ./rootfs +``` + +然后使用`pacstrap`这个`pacman`的初始化工具在`rootfs`安装`base`软件包,最好也顺便装一个`vim`。 + +```bash +sudo pacstrap \ + -C /usr/share/devtools/pacman.conf.d/extra-riscv64.conf + -M ./rootfs \ + base vim +``` + +`extra-riscv64.conf`是在`archlinuxcn/devtools-riscv64`软件包中提供的便利工具,其中包括了`archriscv`该移植的`pacman.conf`文件,当然一般推荐修改一下该文件的镜像站点,以提高安装的速度。 + +然后清理一下`pacman`的缓存文件,缩小`rootfs`的大小,尤其是考虑到后面因为各种操作失误可能会反复解压`rootfs`文件。 + +```bash +sudo pacman \ + --sysroot ./rootfs \ + --sync --clean --clean +``` + +然后设置一下该`rootfs`的`root`账号密码: + +```bash +sudo usermod --root $(realpath ./rootfs) --password $(openssl passwd -6 "$password") root +``` + +就可以将`rootfs`打包为压缩包文件备用了。 + +```bash +sudo bsdtar --create \ + --auto-compress --options "compression-level=9"\ + --xattrs --acls\ + -f archriscv-rootfs.tar.zst -C rootfs/ . +``` + +## 初始化虚拟机镜像 + +首先,创建一个`qcow2`格式的QEMU虚拟机磁盘镜像: + +```bash +qemu-img create -f qcow2 archriscv.img 10G +``` + +其中磁盘的大小可以自行定义。 + +为了能够像正常的磁盘一样进行读写,需要将该文件映射到一个块设备,而这通过`qemu-nbd`程序实现。首先需要加载该程序需要使用的内核驱动程序: + +```bash +sudo modprobe nbd max_part=8 +``` + +命令中的`max_part`指定了最多能够挂载的块设备(文件)个数。然后将该文件虚拟化为一个块设备: + +```bash +sudo qemu-nbd -c /dev/nbd0 archriscv.img +``` + +挂载完毕之后就可以进行初始化虚拟机磁盘镜像的工作了。初始化虚拟机镜像主要涉及到如下几步: + +- 格式化磁盘 +- 安装内核 +- 设置引导程序 + +其中格式化磁盘和后续需要使用的启动引导方式有关系,当使用U-boot这一常用的嵌入式引导系统进行引导时,只需要将磁盘格式化为单个分区即可,只需要在该分区中设置`extlinux/extlinux.conf`文件,至于磁盘的分区表格式是`GPT`还是`MBR`无关紧要。而如果是使用UEFI引导,则需要使用`GPT`分区表,并创建一个ESP(EFI System Partition)分区。这里就以使用UEFI引导的格式化磁盘作为示例,硬盘分区如下表所示: + +| 分区 | 格式 | 挂载点 | 大小 | +| ----------- | ----- | ------ | ---------- | +| /dev/nbd0p1 | FAT32 | /boot | 512M | +| /dev/nbd0p2 | EXT4 | / | 余下的空间 | + +在使用`fdisk`完成磁盘的分区之后,进行格式化并挂载到当前的`mnt`目录中: + +```bash +sudo mkfs.fat -F 32 /dev/nbd0p1 +sudo mkfs.ext4 /dev/nbd0p2 +sudo mkdir mnt +sudo mount /dev/nbd0p2 mnt +sudo mkdir mnt/boot +sudo mount /dev/nbd0p1 mnt/boot +``` + +挂载完成之后解压上一步中备好的`rootfs`: + +```bash +cd mnt +sudo bsdtar -kpxf ../archriscv.tar.zst +``` + +然后使用`systemd-nspawn`工具进入`rootfs`中调用`pacman`安装内核: + +```bash +sudo systemd-nspawn -D mnt pacman \ + --nonconfirm --needed \ + -Syu linux linux-firmware +``` + +接下来分别介绍使用U-boot启动和使用UEFI启动的操作方法。 + +### 使用U-boot启动 + +为了使用U-boot启动,需要手动编译U-boot并打包到OpenSBI中作为QEMU启动的固件。 + +首先编译U-boot: + +```bash +git clone --filter=blob:none -b v2025.04 https://github.com/u-boot/u-boot.git +cd u-boot +make \ + CROSS_COMPILE=riscv64-linux-gnu- \ + qemu-riscv64_smode_defconfig +./scripts/config +make \ + CROSS_COMPILE=riscv64-linux-gnu- \ + olddefconfig +make CROSS_COMPILE=riscv64-linux-gnu- -j18 +``` + +编译好之后检查当前目录下是否存在`u-boot.bin`的固件。 + +然后去编译OpenSBI并将`u-boot.bin`打包进来: + +```bash +git clone --filter=blob:none -b v1.6 https://github.com/riscv-software-src/opensbi.git +cd opensbi +make \ + CROSS_COMPILE=riscv64-linux-gnu- \ + PLATFORM=generic \ + FW_PAYLOAD_PATH=../u-boot/u-boot.bin -j18 +``` + +编译好的三个启动固件应当在`./build/platform/generic/firmware`目录中: + +- `fw_dynamic.bin`使用启动程序设置的地址进行跳转。 +- `fw_jump.bin`跳转到一个固定的地址执行。 +- `fw_payload.bin`执行编译打包的`u-boot`文件,这也是U-boot启动所需要的。 + +编译完成之后,在`mnt`文件中创建`/boot/extlinux/extlinux.conf`文件以告知U-boot启动Linux内核的参数: + +``` +menu title Arch RISC-V Boot Menu +timeout 100 +default linux-fallback + +label linux + menu label Linux linux + kernel /vmlinuz-linux + initrd /initramfs-linux.img + append earlyprintk rw root=UUID=903944ec-a4d3-4820-ac89-c0eac37721f9 rootwait console=ttyS0,115200 + +label linux-fallback + menu label Linux linux (fallback initramfs) + kernel /vmlinuz-linux + initrd /initramfs-linux-fallback.img + append earlyprintk rw root=UUID=903944ec-a4d3-4820-ac89-c0eac37721f9 rootwait console=ttyS0,115200 +``` + +文件中的UUID可以使用如下的指令获得: + +```bash +findmnt mnt -o UUID -n +``` + +其中需要说明的是,文件中指定kernel和intird的时候使用的是`/`而不是`/boot`,这是因为虽然现在把该分区挂载到了`/boot`目录下,但是在U-boot进行启动时会将该分区挂载在`/`目录下,因此需要使用`/`。也是因为同样的原因,当只格式化为一个分区并只使用U-boot进行引导启动时,则需要将目录改为`/boot`。 + +此时即可取消挂载镜像了: + +```bash +sudo umount mnt/boot +sudo umount mnt +sudo qemu-nbd -d /dev/nbd0 +``` + +使用如下的指令即可启动虚拟机: + +```bash +#!/bin/bash + +qemu-system-riscv64 \ + -nographic \ + -machine virt \ + -smp 8 \ + -m 4G \ + -bios opensbi/build/platform/generic/firmware/fw_payload.bin \ + -device virtio-blk-device,drive=hd0 \ + -drive file=archriscv-1.img,format=qcow2,id=hd0,if=none \ + -object rng-random,filename=/dev/urandom,id=rng0 \ + -device virtio-rng-device,rng=rng0 \ + -monitor unix:/tmp/qemu-monitor,server,nowait +``` + +### 使用UEFI启动 + +使用UEFI启动,就需要编译对应的UEFI固件,即开源固件EDK2。 + +```bash +git clone -b edk2-stable202505 --recursive-submodule https://github.com/tianocore/edk2.git +export WORKSPACE=`pwd` +export GCC5_RISCV64_PREFIX=riscv64-linux-gnu- +export PACKAGES_PATH=$WORKSPACE/edk2 +export EDK_TOOLS_PATH=$WORKSPACE/edk2/BaseTools +source edk2/edksetup.sh --reconfig +make -C edk2/BaseTools -j18 +source edk2/edksetup.sh BaseTools +build -a RISCV64 --buildtarget RELEASE -p OvmfPkg/RiscVVirt/RiscVVirtQemu.dsc -t GCC5 +``` + +编译之后得到的两份固件应该在`Build/RiscVVirtQemu/RELEASE_GCC5/FV`目录下: + +- `RISCV_VIRT_CODE.fd`固件的代码部分。 +- `RISCV_VIRT_VARS.fd`固件的数据部分,可以被UEFI工具修改。 + +在启动之前首先将这两个文件填充到32M的大小以符合QEMU对于`pflash`文件的大小要求: + +```bash +truncate -s 32M Build/RiscVVirtQemu/RELEASE_GCC5/FV/RISCV_VIRT_CODE.fd +truncate -s 32M Build/RiscVVirtQemu/RELEASE_GCC5/FV/RISCV_VIRT_VARS.fd +``` + +然后就可以使用如下的指令启动QEMU虚拟机了,这里复用U-boot中编译的OpenSBI固件,如果没有执行这一步可以选择删除下面指令中的`-bios`选项,使用QEMU自带的OpenSBI实现。 + +```bash +#!/bin/bash + +qemu-system-riscv64 \ + -M virt,pflash0=pflash0,pflash1=pflash1,acpi=off \ + -m 4096 -smp 8 -nographic \ + -bios opensbi/build/platform/generic/firmware/fw_dynamic.bin \ + -blockdev node-name=pflash0,driver=file,read-only=on,filename=Build/RiscVVirtQemu/RELEASE_GCC5/FV/RISCV_VIRT_CODE.fd \ + -blockdev node-name=pflash1,driver=file,filename=Build/RiscVVirtQemu/RELEASE_GCC5/FV/RISCV_VIRT_VARS.fd \ + -device virtio-blk-device,drive=hd0 \ + -drive file=archriscv-1.img,format=qcow2,id=hd0,if=none \ + -netdev user,id=n0 -device virtio-net,netdev=n0 \ + -monitor unix:/tmp/qemu-monitor,server,nowait +``` + +但是,这一步启动并不会进入Linux内核,这是因为还没有向UEFI注册需要启动的系统,使得UEFI可以识别到可以执行启动的磁盘。在普通的系统安装上,由于是使用安装镜像直接从UEFI启动的,在`chroot`环境中可以直接使用`grub-install`直接安装,但是在目前的`systemd-nspawn`环境中是缺少`efivarfs`等必要的文件系统的。 + +因此可以首先尝试在启动之后进入`UEFI Shell`之后,手动设置参数直接启动Linux内核。 + +![image-20250527134233659](./linux-distribution-from-zero/image-20250527134233659.webp) + +进入`UEFI Shell`之后,首先选择文件系统`FS0:`,然后使用如下的指令尝试手动启动Linux内核: + +```bash +\vmlinuz-linux initrd=\initramfs-linux.img earlyprintk rw root=UUID=903944ec-a4d3-4820-ac89-c0eac37721f9 rootwait console=ttyS0,115200 +``` + +但是可能会遇到如下的问题: + +![image-20250527134421403](./linux-distribution-from-zero/image-20250527134421403.webp) + +这里也尝试了使用`mkinitcpio`生成的Unified Kernel Image,放在`EFI/Linux`文件目录下,同样遇到了如下的问题: + +![image-20250527134540583](./linux-distribution-from-zero/image-20250527134540583.webp) + +暂时不清楚这是EDK2的问题还是这里操作的问题,至少能确定这里编译内核时是启用了`CONFIG_EFI_STUB`选项的。 + +因此这里使用`grub`方式尝试绕过这个问题,首先在`systemd-nswpan`环境中使用如下的指令安装`grub`,虽然会因为环境问题报错,但是手动查看可以发现安装脚本已经将`grubriscv64.efi`文件复制到`/boot/EFI/GRUB`目录了。 + +此时再次进入`UEFI Shell`,手动指定启动`grub`,所幸这次启动成功,此时我们再从`grub shell`中尝试启动Linux,使用的指令如下: + +```bash +linux (hd0,gpt1)/vmlinuz-linux earlyprintk rw root=UUID=903944ec-a4d3-4820-ac89-c0eac37721f9 rootwait console=ttyS0,115200 +initrd (hd0,gpt1)/initramfs-linux.img +boot +``` + +![image-20250527135748547](./linux-distribution-from-zero/image-20250527135748547.webp) + +此时就可以正常的进入完成完整的安装过程了。 + +> 首次启动的时候推荐使用`fallback initramfs`,因为在`chroot`环境中生成的驱动可能不全。如果在使用主要的`initramfs`进行启动时遇到了无法挂载真`/`目录而进入`emergency shell`,同时在该Shell中也无法发现虚拟机的磁盘,就极有可能是系统缺少对应的驱动无法挂载。 +> +> 例如在`chroot`环境中生成的`initcpio`包含如下的模块: +> +> ![image-20250325160729310](./linux-distribution-from-zero/image-20250325160729310.webp) +> +> 而在进入系统之后,重新运行`mkinitcpio`之后包含的模块如下所示: +> +> ![image-20250325161310820](./linux-distribution-from-zero/image-20250325161310820.webp) + diff --git a/source/posts/linux-distribution-from-zero/image-20250325160729310.webp b/source/posts/linux-distribution-from-zero/image-20250325160729310.webp new file mode 100644 index 0000000..956531f --- /dev/null +++ b/source/posts/linux-distribution-from-zero/image-20250325160729310.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48ab3dd92f703da5ee676d072c69e393c6b7f2f169aacb52d626f1859b21d044 +size 19452 diff --git a/source/posts/linux-distribution-from-zero/image-20250325161310820.webp b/source/posts/linux-distribution-from-zero/image-20250325161310820.webp new file mode 100644 index 0000000..681ccc1 --- /dev/null +++ b/source/posts/linux-distribution-from-zero/image-20250325161310820.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:903133fdccb46ef5b6c42dd30d0a0be92f0b7e93007ec3500a7327a1bda7a3de +size 16988 diff --git a/source/posts/linux-distribution-from-zero/image-20250527134233659.webp b/source/posts/linux-distribution-from-zero/image-20250527134233659.webp new file mode 100644 index 0000000..7187f8c --- /dev/null +++ b/source/posts/linux-distribution-from-zero/image-20250527134233659.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbddead6d887e378caa31becd335e040b3b0bddf9ad90cb18d48e0b2fbf2e045 +size 21630 diff --git a/source/posts/linux-distribution-from-zero/image-20250527134421403.webp b/source/posts/linux-distribution-from-zero/image-20250527134421403.webp new file mode 100644 index 0000000..f582de5 --- /dev/null +++ b/source/posts/linux-distribution-from-zero/image-20250527134421403.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c76d0f31d20a60aa1945fefcf7e557cb5c44c0575557916cfca586ebe34bc665 +size 19944 diff --git a/source/posts/linux-distribution-from-zero/image-20250527134540583.webp b/source/posts/linux-distribution-from-zero/image-20250527134540583.webp new file mode 100644 index 0000000..6377a90 --- /dev/null +++ b/source/posts/linux-distribution-from-zero/image-20250527134540583.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10a621726031480fca3d44e9a7c24f9e645bdb99c955c009c433b76626269104 +size 21798 diff --git a/source/posts/linux-distribution-from-zero/image-20250527135748547.webp b/source/posts/linux-distribution-from-zero/image-20250527135748547.webp new file mode 100644 index 0000000..738dcfb --- /dev/null +++ b/source/posts/linux-distribution-from-zero/image-20250527135748547.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:705f545ea119e2559aabab0b25061bd2cc62c92169824561df7212a36f847fc7 +size 10152 diff --git a/YaeBlog/source/posts/linux-genshin-cloud.md b/source/posts/linux-genshin-cloud.md similarity index 100% rename from YaeBlog/source/posts/linux-genshin-cloud.md rename to source/posts/linux-genshin-cloud.md diff --git a/YaeBlog/source/posts/llvm-naive-0.md b/source/posts/llvm-naive-0.md similarity index 99% rename from YaeBlog/source/posts/llvm-naive-0.md rename to source/posts/llvm-naive-0.md index 100a501..4ec7eb4 100644 --- a/YaeBlog/source/posts/llvm-naive-0.md +++ b/source/posts/llvm-naive-0.md @@ -7,6 +7,7 @@ tags: - 技术笔记 --- + 为什么说LLVM是神? @@ -55,7 +56,7 @@ clang hello.c -o hello 编译完成之后可以正常执行: -![image-20240819213039409](./llvm-naive-0/image-20240819213039409.png) +![image-20240819213039409](./llvm-naive-0/image-20240819213039409.webp) 然后尝试将这个C语言文件编译为LLVM的字节码形式: @@ -71,7 +72,7 @@ clang -O3 -emit-llvm hello.c -c -o hello.bc lli hello.bc ``` -![image-20240819213624927](./llvm-naive-0/image-20240819213624927.png) +![image-20240819213624927](./llvm-naive-0/image-20240819213624927.webp) 可以使用反编译器将字节码转换为人类可读的文本形式: @@ -152,7 +153,7 @@ main: # @main 下面这张图展示了LLVM中各种文件的转换关系和使用的对应工具。 -![image-20240820221413791](./llvm-naive-0/image-20240820221413791.png) +![image-20240820221413791](./llvm-naive-0/image-20240820221413791.webp) ## LLVM IR @@ -268,5 +269,5 @@ entry: 使用`lli`解释器可以直接运行这段代码: -![image-20240825171858276](./llvm-naive-0/image-20240825171858276.png) +![image-20240825171858276](./llvm-naive-0/image-20240825171858276.webp) diff --git a/source/posts/llvm-naive-0/image-20240819213039409.webp b/source/posts/llvm-naive-0/image-20240819213039409.webp new file mode 100644 index 0000000..640b62d --- /dev/null +++ b/source/posts/llvm-naive-0/image-20240819213039409.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:672011acf34f27be68cb1cae19598aa4f0da697a365b025b60a84a5891563098 +size 8006 diff --git a/source/posts/llvm-naive-0/image-20240819213624927.webp b/source/posts/llvm-naive-0/image-20240819213624927.webp new file mode 100644 index 0000000..2f52fec --- /dev/null +++ b/source/posts/llvm-naive-0/image-20240819213624927.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ff4a658ea5e4805c2193276b1fb4cd688865f0831bb76fc371e26137572bc3d +size 6892 diff --git a/source/posts/llvm-naive-0/image-20240820221413791.webp b/source/posts/llvm-naive-0/image-20240820221413791.webp new file mode 100644 index 0000000..94fc168 --- /dev/null +++ b/source/posts/llvm-naive-0/image-20240820221413791.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68df83cedd473725b0379fa786739408f868863b6e205a401a4cd4335812f653 +size 33988 diff --git a/source/posts/llvm-naive-0/image-20240825171858276.webp b/source/posts/llvm-naive-0/image-20240825171858276.webp new file mode 100644 index 0000000..f9658b7 --- /dev/null +++ b/source/posts/llvm-naive-0/image-20240825171858276.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba4bd9fbd90f0f81bb1fd32e54d8c249a4f7a9fc4ea2b2ab968644e49132eedc +size 6948 diff --git a/YaeBlog/source/posts/minecraft-wayland.md b/source/posts/minecraft-wayland.md similarity index 98% rename from YaeBlog/source/posts/minecraft-wayland.md rename to source/posts/minecraft-wayland.md index 645915f..bfdf931 100644 --- a/YaeBlog/source/posts/minecraft-wayland.md +++ b/source/posts/minecraft-wayland.md @@ -1,11 +1,12 @@ --- title: 让Minecraft运行在Wayland下 +date: 2024-01-12T20:10:06.0000000 tags: - - Linux - - 生活小妙招 -date: 2024-1-12 20:10:06 +- Linux +- 生活小妙招 --- + 让Minecraft游戏使用`Wayland`显示协议。 @@ -127,15 +128,15 @@ export __GL_THREADED_OPTIMIZATIONS=0 然后就是设置Minecraft使用我们自行编译的图形库,需要设置`java`启动参数,我使用的[hmcl](https://github.com/huanghongxun/HMCL)这款启动器,可以在设置里面很方便的设置启动参数: -![image-20240105212744116](./minecraft-wayland/image-20240105212744116.png) +![image-20240105212744116](./minecraft-wayland/image-20240105212744116.webp) 但是,如果你使用的是`nvidia`显卡,这里还会遇到一个问题: -![image-20240105213439528](./minecraft-wayland/image-20240105213439528.png) +![image-20240105213439528](./minecraft-wayland/image-20240105213439528.webp) 这个问题从一些资料显示仍然是老黄整的好活: -![image-20240105213942445](./minecraft-wayland/image-20240105213942445.png) +![image-20240105213942445](./minecraft-wayland/image-20240105213942445.webp) 设置环境变量解决,再次强烈推荐[hmcl](https://github.com/huanghongxun/HMCL)启动器,可以方便的设置环境变量。 diff --git a/source/posts/minecraft-wayland/image-20240105212744116.webp b/source/posts/minecraft-wayland/image-20240105212744116.webp new file mode 100644 index 0000000..920799e --- /dev/null +++ b/source/posts/minecraft-wayland/image-20240105212744116.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b88af664411089105ab95d3c2e8678796dab177484664251c073eef31356f2f +size 47110 diff --git a/source/posts/minecraft-wayland/image-20240105213439528.webp b/source/posts/minecraft-wayland/image-20240105213439528.webp new file mode 100644 index 0000000..2e377fb --- /dev/null +++ b/source/posts/minecraft-wayland/image-20240105213439528.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c2489337160ab1eb5620d5cdee9d927b5a4dab2d90d5d6c571a7641acecbe45 +size 7410 diff --git a/source/posts/minecraft-wayland/image-20240105213942445.webp b/source/posts/minecraft-wayland/image-20240105213942445.webp new file mode 100644 index 0000000..dee85cb --- /dev/null +++ b/source/posts/minecraft-wayland/image-20240105213942445.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40096bb91cfc78eb62c94487ca8fb8506e7390d4800cd07aee24b884801cb70a +size 65760 diff --git a/source/posts/mlir-standalone.md b/source/posts/mlir-standalone.md new file mode 100644 index 0000000..578c77f --- /dev/null +++ b/source/posts/mlir-standalone.md @@ -0,0 +1,164 @@ +--- +title: 构建运行基于MLIR的独立项目 +date: 2025-03-19T20:57:31.1928528+08:00 +tags: +- 技术笔记 +- LLVM +--- + + +MLIR是多层次中间表示形式(Multi-Level Intermediate Representation),是LLVM项目中提供的一项新编译器开发基础设施,使得编译器开发者能够在源代码和可执行代码之间定义多层IR来保留程序信息指导编译优化。本博客指导如何创建一个独立(out-of-tree)的MLIR项目。 + + + +## 编译LLVM和MLIR + +考虑到大多数的Linux发行版在打包LLVM时不会编译MLIR,因此自行编译安装包括MLIR项目的LLVM就成为开发独立MLIR项目的前置条件。 + +首先在GitHub上下载LLVM的源代码包,我这里选择最新的稳定版本`20.1.0`。 + +```shell +wget https://github.com/llvm/llvm-project/releases/download/llvmorg-20.1.0/llvm-project-20.1.0.src.tar.xz +``` + +下载之后解压进入,准备进行构建。 + +```shell +tar xvf llvm-project-20.1.0.src.tar.xz +cd llvm-project-20.1.0.src +``` + +创建`build`文件夹,使用下面的命令进行生成构建文件。在这里选择使用`Release`构建类型,安装的位置是`~/.local/share/llvm`文件夹,构建的项目包括`llvm`、`clang`和`mlir`三个项目,并指定使用系统上的`clang`和`clang++`编译器作为编译过程中使用的编译器。 + +```shell +mkdir build +cd build +cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="/home/ricardo/.local/share/llvm-20.1.0" -DLLVM_ENABLE_PROJECTS="llvm;clang;mlir" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DLLVM_INSTALL_UTILS=true ../llvm +``` + +![image-20250319192618697](./mlir-standalone/image-20250319192618697.webp) + +生成构建文件之后使用`ninja`进行构建。 + +```shell +ninja +``` + +![image-20250319194742171](./mlir-standalone/image-20250319194742171.webp) + +构建在我的i5-13600K上大约需要20分钟。 + +构建完成之后进行安装。 + +```shell +ninja install +``` + +## 编译MLIR官方的独立项目 + +MLIR的官方提供了一个独立项目,项目文件夹在`mlir/examples/standalone`中,将这个文件夹中的内容复制到我们需要的地方,尝试使用上面构建的`mlir`进行构建。 + +```shell +cp -r ~/Downloads/llvm-project-20.1.0.src/mlir/examples/standalone mlir-standalone +cd mlir-standalone +``` + +### 不启用测试 + +编译过程中可能遇到的最大问题是`llvm-lit`,这个使用`python`编写的LLVM集成测试工具,在`standalone`的`README.md`中要求编译过程中使用`LLVM_EXTERNAL_LIT`变量指定到LLVM编译过程中生成的`llvm-lit`可执行文件。 + +> 也许就是因为`llvm-lit`是用Python撰写的,所以`llvm-lit`不会安装到`PREFIX`指定的位置。 + +不过我们可以禁用测试(笑)。在`CMakeLists.txt`文件中注释对于测试文件夹的添加: + +```cmake +add_subdirectory(include) +add_subdirectory(lib) +if(MLIR_ENABLE_BINDINGS_PYTHON) + message(STATUS "Enabling Python API") + add_subdirectory(python) +endif() +#add_subdirectory(test) +add_subdirectory(standalone-opt) +add_subdirectory(standalone-plugin) +add_subdirectory(standalone-translate) + +``` + +回到构建文件夹,使用如下的`cmake`指令生成构建文件。 + +```shell +export LLVM_DIR=/home/ricardo/.local/share/llvm-20.1.0 +cmake -G Ninja -DMLIR_DIR=$LLVM_DIR/lib/cmake/mlir .. +``` + +可以顺利通过编译。 + +![image-20250319202218503](./mlir-standalone/image-20250319202218503.webp) + +### 启用测试 + +但是测试还是非常重要的。我们尝试启动测试看看,取消对于测试文件夹的注释: + +```shell +rm -rf build && mkdir build && cd build +cmake -G Ninja -DMLIR_DIR=$LLVM_DIR/lib/cmake/mlir .. +``` + +很好顺利报错,报错的提示是缺失`FileCheck`、`count`和`not`。 + +![image-20250319202553644](./mlir-standalone/image-20250319202553644.webp) + +那么按照`README.md`中的提示添加上来自构建目录的`llvm-lit`会怎么样呢? + +```shell +rm -rf build && mkdir build && cd build +export LLVM_BUILD_DIR=/home/ricardo/Downloads/llvm-project-20.1.0.src/build +cmake -G Ninja -DMLIR_DIR=$LLVM_DIR/lib/cmake/mlir -DLLVM_EXTERNAL_LIT=$LLVM_BUILD_DIR/bin/llvm-lit .. +``` + +同样的报错,看来问题不是出在这里。 + +经过对于LLVM文档的仔细研究,发现原来是没有启动这个变量: + +![image-20250319204057832](./mlir-standalone/image-20250319204057832.webp) + +遂修改最初的LLVM编译指令。 + +重新运行来自`README.md`的构建文件生成指令之后,测试也完美运行通过: + +```shell +ninja test-standalone +``` + +![image-20250319204522857](./mlir-standalone/image-20250319204522857.webp) + +不过这个还是有一点令我不是特别满意,这依赖了一个来自于构建目录的工具`llvm-lit`,如果我编译安装的时候眼疾手快的删除了编译目录不就完蛋了。而且我都**standalone**了还依赖似乎有点说不过去? + +于是发现了一篇从`pip`上下载使用`llvm-lit`的[博客](https://medium.com/@mshockwave/using-llvm-lit-out-of-tree-5cddada85a78)和一个LLVM Discourse上面的[帖子](https://discourse.llvm.org/t/running-llvm-lit-on-external-project-test-file-derived-from-standalone-fails/67787),遂进行尝试。 + +首先在当前目录下创建一个虚拟环境,并下载安装`llvm-lit`。 + +```shell +python -m venv .llvm-lit +source .llvm-lit/bin/activate +pip install lit +``` + +~~不过这个库似乎没有提供运行入口点,需要我们手动创建一个可执行的`python`文件:~~ + +```python +#!/usr/bin/env python +from lit.main import main +if __name__ == '__main__': + main() +``` + +经哥们纠正说`lit`包在某个版本之后会安装`lit`的可执行文件,在安装之后可以直接在命令行调用`lit`。因此在激活虚拟环境之后,`cmake`中直接在`LLVM_EXTERNAL_LIT`配置为`$(which lit)`即可。 + +```shell +cmake -G Ninja -DMLIR_DIR=$LLVM_DIR/lib/cmake/mlir -DLLVM_EXTERNAL_LIT=$(which lit) .. +ninja test-standalone +``` + +![image-20250319205520649](./mlir-standalone/image-20250319205520649.webp) diff --git a/source/posts/mlir-standalone/image-20250319192618697.webp b/source/posts/mlir-standalone/image-20250319192618697.webp new file mode 100644 index 0000000..267dae8 --- /dev/null +++ b/source/posts/mlir-standalone/image-20250319192618697.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5af895b246f31588b64715d5b8efe265fc35774c7b534163de99fb07970f48c +size 7738 diff --git a/source/posts/mlir-standalone/image-20250319194742171.webp b/source/posts/mlir-standalone/image-20250319194742171.webp new file mode 100644 index 0000000..870f11a --- /dev/null +++ b/source/posts/mlir-standalone/image-20250319194742171.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8916648fa02bdef28250f39ae36e2d0fe894ac7bb653f574e85ca2898beaf974 +size 8086 diff --git a/source/posts/mlir-standalone/image-20250319202218503.webp b/source/posts/mlir-standalone/image-20250319202218503.webp new file mode 100644 index 0000000..57ddb5a --- /dev/null +++ b/source/posts/mlir-standalone/image-20250319202218503.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3440df949ceee03748b6c0519610a67aad977a1d0101447d7b6c39c5dbb632df +size 9598 diff --git a/source/posts/mlir-standalone/image-20250319202553644.webp b/source/posts/mlir-standalone/image-20250319202553644.webp new file mode 100644 index 0000000..6c682a7 --- /dev/null +++ b/source/posts/mlir-standalone/image-20250319202553644.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33826ceb4246b65b55a44fe3d1c7e9dcb72a5ee7e917fc6c18e944e9e7a3e1aa +size 21426 diff --git a/source/posts/mlir-standalone/image-20250319204057832.webp b/source/posts/mlir-standalone/image-20250319204057832.webp new file mode 100644 index 0000000..865a57c --- /dev/null +++ b/source/posts/mlir-standalone/image-20250319204057832.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a941d0a6f4e40bb4002e082541ba96fbbc615e3cbc1ecae0f7e376090a802c6 +size 14166 diff --git a/source/posts/mlir-standalone/image-20250319204522857.webp b/source/posts/mlir-standalone/image-20250319204522857.webp new file mode 100644 index 0000000..db2dd15 --- /dev/null +++ b/source/posts/mlir-standalone/image-20250319204522857.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65136396fea6c22f1775523bd8c1e762c0ab6adf366e66c5b735ce1afcd1607f +size 15996 diff --git a/source/posts/mlir-standalone/image-20250319205520649.webp b/source/posts/mlir-standalone/image-20250319205520649.webp new file mode 100644 index 0000000..2e527e0 --- /dev/null +++ b/source/posts/mlir-standalone/image-20250319205520649.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c680ecc320ce7801e0e54c149d9ef619019405d1f0ba1031c5c5605d40f736c3 +size 10614 diff --git a/source/posts/msbuild-generate-files.md b/source/posts/msbuild-generate-files.md new file mode 100644 index 0000000..8fcd709 --- /dev/null +++ b/source/posts/msbuild-generate-files.md @@ -0,0 +1,147 @@ +--- +title: 如何在MSBuild中复制生成的文件到发布目录中 +date: 2025-03-20T22:33:21.6955385+08:00 +tags: +- 技术笔记 +- dotnet +--- + + + 如何使用`MSBuild`将构建过程中生成文件复制到生成目录中? + + + +### 遇到的问题 + +最近在尝试在`blazor`项目中使用`tailwindcss`作为`css`工具类的提供工具,而不是使用老旧的`bootstrap`框架,不过使用`tailwindcss`需要在项目构建时使用`tailwindcss`工具扫描文件中使用到的`css`属性并生成最终的`css`文件,这就带来了在构建时运行`tailwindcss`生成并复制到输出目录的需求。 + +由于我是使用`pnpm`作为前端管理工具,我在项目的`csproj`文件中添加了下面的`Target`来生成文件: + +```xml + + + + + + + + + + + + + + + + +``` + +这套生成逻辑在本地工作良好,但是却在CI上运行时出现了问题:`CI`上打包的`Docker`镜像中没有`tailwind.g.css`文件,导致最终部署的站点丢失了所有的格式。 + +### 产生问题的原因 + +经过反复实验,我发现只有在构建之前`wwwroot`目录中已经存在`tailwind.g.css`文件的情况下,`MSBuild`才会将生成的文件复制到最终的输出目录中。但是在`CI`环境下,因为使用`.gitignore`没有将`*.g.css`文件添加到代码管理,因此`CI`运行构建之前没有该文件,因此构建的结果中也没有该文件。 + +仔细研究`MSBuild`的文档和网络上的[分享](https://gist.github.com/BenVillalobos/c671baa1e32127f4ab582a5abd66b005),我意识到这是由于`MSBuild`的构建流程导致的,MSBuild`的构建流程分成两个大的阶段: + +- 评估阶段(Evaluation Phase) + + 在这个阶段,`MSBuild`将会运行读取所有的配置文件,创建需要的属性,展开所有的`glob`,建立好整个构建流程。 + +- 执行阶段(Execution Phase) + + 在这个阶段,`MSBuild`将按照上一阶段执行的属性执行实际的构建指令。 + +这两个阶段的划分就导致在生成阶段才生成的文件不会被包含在复制文件的指令中,因此他们不会被拷贝到最终的输出目录中。 + +>这和`cmake`的构建过程很像,首先调用`cmake`生成一些构建指令,在调用实际的构建指令构建二进制文件。 + +因此这类问题的推荐解决办法是手动将这些文件添加到构建流程中,即在`BeforeBuild`目标调用之前使用`Content`和`None`等项。 + +### 解决问题 + +总结上述的解决问题方法,我在上面的构建流程中添加了如下的`None`项: + +```xml + + + + + + + + + + + + + + + + + + + + + +``` + +在运行构建之后,在最终的`publish`文件夹的`wwroot`文件夹中就可以找到`tailwind.g.css`文件。 + +不过我还想进行一点优化,`MSBuild`文档中建议将自动生成的文件放在`IntermediateOutputPath`,也就是`obj`文件加中,因此这里尝试将`tailwind.g.css`文件生成到`IntermediateOuputPath`中,优化之后的`Target`项长这个样子: + +```xml + + + + + + + + + + + + + + + + + + + + + +``` + +经过测试,这套生成逻辑在`blazor`类库环境下也可以正常运行,类库的文件会被正确地生成到`wwwroot/_content//`文件夹下面。 + +### 新的问题 + +在上述代码合并之后,我在后续开发过程中却遇到的了新的问题:在开发环境下项目运行的目录是源代码目录,而此时的`wwwroot`目录下面没有`tailwind.g.css`文件,此时网站再次丢失了样式,而如果使用`pnpm tailwindcss -i wwroot/tailwind.css -o wwwroot/tailwind.g.css`生成文件的话,却会遇到构建错误: + +![image-20250325150841442](./msbuild-generate-files/image-20250325150841442.webp) + +这是因为`.NET SDK`也会尝试将已经存在的`wwwroot/tailwind.g.css`复制到输出文件中,这就会造成冲突。 + +因此为了让开发环境和测试环境可以共存,我让`TailwindGenerate`目标只在`dotnet publish`运行,而在开发环境中使用`pnpm tailwindcss`手动生成`CSS`文件。 + +```xml + + + + + + + + + + + + + + + + +``` + diff --git a/source/posts/msbuild-generate-files/image-20250325150841442.webp b/source/posts/msbuild-generate-files/image-20250325150841442.webp new file mode 100644 index 0000000..d76a83c --- /dev/null +++ b/source/posts/msbuild-generate-files/image-20250325150841442.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5ad19fed6a5070df683cd33dff436198ca19e7a0db23eb1348ddee1df7c6d7d +size 21830 diff --git a/YaeBlog/source/posts/parser-combinator-performance.md b/source/posts/parser-combinator-performance.md similarity index 99% rename from YaeBlog/source/posts/parser-combinator-performance.md rename to source/posts/parser-combinator-performance.md index fe49651..c13a3cf 100644 --- a/YaeBlog/source/posts/parser-combinator-performance.md +++ b/source/posts/parser-combinator-performance.md @@ -1,11 +1,12 @@ --- title: 解析器组合子和LR(1)分析方法的性能比较 +date: 2024-08-19T14:31:00.0000000 tags: - - 编译原理 - - 技术笔记 -date: 2024-08-19 14:31:00 +- 编译原理 +- 技术笔记 --- + 在使用解析器组合子编写编译器前端时,其与LR分析方法之间的性能差距是开发人员关心的重点问题之一。 @@ -146,7 +147,7 @@ public class GrammarParserBenchmark 在对原始数据进行数据处理之后我们绘制了如下的图。图中的横轴是输入测试文件的编号,图中的纵轴是`CanonSharp`解析器和`Canon`解析器运行时间的比值。从图中可以看出`CanonSharp`解析器的运行时间大约是`Canon`解析器运行时间的65到75倍,在某些极端的情况下可能会达到90倍。 -![image-20240819140523087](./parser-combinator-performance/image-20240819140523087.png) +![image-20240819140523087](./parser-combinator-performance/image-20240819140523087.webp) ## 结论 diff --git a/source/posts/parser-combinator-performance/image-20240819140523087.webp b/source/posts/parser-combinator-performance/image-20240819140523087.webp new file mode 100644 index 0000000..b621851 --- /dev/null +++ b/source/posts/parser-combinator-performance/image-20240819140523087.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a78ceaf7a4bab37963057c792aae1373385042c3dce61dca04f288d3b3458037 +size 14538 diff --git a/YaeBlog/source/posts/parser-combinator.md b/source/posts/parser-combinator.md similarity index 99% rename from YaeBlog/source/posts/parser-combinator.md rename to source/posts/parser-combinator.md index baf2b98..cc2130a 100644 --- a/YaeBlog/source/posts/parser-combinator.md +++ b/source/posts/parser-combinator.md @@ -1,12 +1,13 @@ --- title: 使用Parser Combinator编写编译器前端 +date: 2024-08-13T18:09:10.0000000 tags: - - 编译原理 - - C# - - 技术笔记 -date: 2024-08-13 18:09:10 +- 编译原理 +- C# +- 技术笔记 --- + 在函数式编程思想的指导下,使用Parser Combinator编写编译器前端。 @@ -402,7 +403,7 @@ public class C { -![image-20240813214315576](./parser-combinator/image-20240813214315576.png) +![image-20240813214315576](./parser-combinator/image-20240813214315576.webp) 跳过组合子的实现则是使用我们之前提过的修改组合子(Fix Parser)进行的。 @@ -454,9 +455,9 @@ public static Parser> Quote(thi Pascal-S语言的词法约定如下所示: -![image-20240813220521028](./parser-combinator/image-20240813220521028.png) +![image-20240813220521028](./parser-combinator/image-20240813220521028.webp) -![image-20240813220530717](./parser-combinator/image-20240813220530717.png) +![image-20240813220530717](./parser-combinator/image-20240813220530717.webp) 据此,我们可以开始编写对应的词法分析器。首先给出一个词法令牌的规定,将词法令牌分类为: diff --git a/source/posts/parser-combinator/image-20240813214315576.webp b/source/posts/parser-combinator/image-20240813214315576.webp new file mode 100644 index 0000000..a549db4 --- /dev/null +++ b/source/posts/parser-combinator/image-20240813214315576.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92c8739d775414fe9e06c338bc2635529da4e2066ee76273cce42cc0d67020bf +size 13462 diff --git a/source/posts/parser-combinator/image-20240813220521028.webp b/source/posts/parser-combinator/image-20240813220521028.webp new file mode 100644 index 0000000..68bc9cf --- /dev/null +++ b/source/posts/parser-combinator/image-20240813220521028.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fefce83495bdc3ae267b93ec2e46e7979fe528329c69156b61e2956abdbd216e +size 60562 diff --git a/source/posts/parser-combinator/image-20240813220530717.webp b/source/posts/parser-combinator/image-20240813220530717.webp new file mode 100644 index 0000000..a60dbb8 --- /dev/null +++ b/source/posts/parser-combinator/image-20240813220530717.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:106855c0f051d0130474330fe5944752bb6236dea870584e9152ac09efc8352e +size 53078 diff --git a/source/posts/podman-dns-failed.md b/source/posts/podman-dns-failed.md new file mode 100644 index 0000000..5b7c75c --- /dev/null +++ b/source/posts/podman-dns-failed.md @@ -0,0 +1,130 @@ +--- +title: Podman容器中特定域名解析失败问题排查 +date: 2025-10-22T17:42:07.7738936+08:00 +tags: +- 技术笔记 +- Bug Collections +--- + +最难以置信的一集。 + + + +## 问题现象 + +在折腾我的Gitea CI/CD管线的时候我,我尝试进行一项重要的变更:在运行CI任务时,任务中的容器支持从Docker out of Docker(DoD)迁移到Podman inside Podman(PiP)。但是在执行该项迁移的过程中,我遇到了如下一个非常神秘的Bug。 + +在CI任务运行的过程中,我发现一些**特定的**、**稳定的**域名无法在任务容器中正常解析并请求。目前注意到的域名有如下两个: + +- dot.net +- rcj.bupt-hpc.cn + +在容器内使用`curl http://dot.net -vvvvi`时,日志中重复出现如下内容。 + +``` +10:35:43.261007 [0-x] == Info: [MULTI] [INIT] added to multi, mid=1, running=1, total=2 +10:35:43.261252 [0-x] == Info: [MULTI] [INIT] multi_wait(fds=1, timeout=0) tinternal=0 +10:35:43.261533 [0-x] == Info: [MULTI] [INIT] -> [SETUP] +10:35:43.261690 [0-x] == Info: [MULTI] [SETUP] -> [CONNECT] +10:35:43.261838 [0-x] == Info: [READ] client_reset, clear readers +10:35:43.262042 [0-0] == Info: [MULTI] [CONNECT] [CPOOL] added connection 0. The cache now contains 1 members +10:35:43.262315 [0-0] == Info: [DNS] init threaded resolve of rcj.bupt-hpc.cn:9090 +10:35:43.262597 [0-0] == Info: [DNS] resolve thread started for of rcj.bupt-hpc.cn:9090 +10:35:43.262854 [0-0] == Info: [MULTI] [CONNECT] [TIMEOUT] set ASYNC_NAME to expire in 1000ns +10:35:43.263081 [0-0] == Info: [MULTI] [CONNECT] -> [RESOLVING] +10:35:43.263254 [0-0] == Info: [MULTI] [RESOLVING] multi_wait pollset[fd=4 IN], timeouts=1 +10:35:43.263500 [0-0] == Info: [MULTI] [RESOLVING] [TIMEOUT] ASYNC_NAME expires in 355ns +10:35:43.263743 [0-0] == Info: [MULTI] [RESOLVING] multi_wait(fds=2, timeout=1) tinternal=1 +10:35:43.265041 [0-0] == Info: [DNS] Curl_resolv_check() -> 0, missing +10:35:43.265218 [0-0] == Info: [MULTI] [RESOLVING] multi_wait pollset[fd=4 IN], timeouts=1 +10:35:43.265497 [0-0] == Info: [MULTI] [RESOLVING] [TIMEOUT] ASYNC_NAME expires in 1545ns +10:35:43.265736 [0-0] == Info: [MULTI] [RESOLVING] multi_wait(fds=2, timeout=2) tinternal=2 +10:35:43.268052 [0-0] == Info: [DNS] Curl_resolv_check() -> 0, missing +10:35:43.268236 [0-0] == Info: [MULTI] [RESOLVING] multi_wait pollset[fd=4 IN], timeouts=1 +10:35:43.268471 [0-0] == Info: [MULTI] [RESOLVING] [TIMEOUT] ASYNC_NAME expires in 3582ns +10:35:43.268688 [0-0] == Info: [MULTI] [RESOLVING] multi_wait(fds=2, timeout=4) tinternal=4 +``` + +## 初步假设与排除过程 + +这个问题非常奇怪,在问题排查初期,我进行了如下的操作。 + +1. 首先单独测试容器中的域名解析,使用`dig rcj.bupt-hpc.cn`进行解析测试,解析的结果符合预期,使用的DNS服务器是当前的容器的Podman网关地址10.17.14.1。 +2. 独立启动容器,而不是使用CI管线启动的容器,使用`podman run --rm -it --privileged ccr.ccs.tencentyun.com/jackfiled/runner-base:latest bash -l`创建容器,对上述出现问题的两个域名上的服务进行请求,没有问题,排除镜像本身的问题。 + +此时已经束手无策了,遂尝试AI辅助进行排查。 + +## AI辅助下的问题排查 + +### dig工具和libc的解析机制之前存在差异 + +AI首先提到,上面测试的dig工具和其他应用使用的libc解析机制之间存在差异: + +- `dig` 是直接使用 DNS 协议向 DNS 服务器发查询请求(绕过系统的解析器)。 +- `curl`(以及大多数应用)依赖系统 C 库(如 glibc)的 `getaddrinfo()` 函数,它会读取 `/etc/nsswitch.conf` 和 `/etc/resolv.conf`,并可能使用 `systemd-resolved`、`nscd` 等服务。 + +于是需要使用`getent`工具测试libc库的DNS解析是否存在问题,测试发现解析结果正常。 + +### IPv6 优先导致超时或失败 + +AI还提到,如果系统尝试先用 IPv6 解析(AAAA 记录),但网络不支持 IPv6,可能导致 `curl` 卡住或失败,而 `dig` 默认查 A 记录。 + +于是在使用`curl`时手动指定使用的网络协议,的确当使用`curl -4 http://dot.net`时可以正常的完成请求。 + +虽然这一方法可以解决问题,但是其给出的理由似乎无法解释我遇到的问题,问题域名中的rcj.bupt-hpc.cn并没有任何的AAAA解析记录,执行 `getent ahosts rcj.bupt-hpc.cn` 仅返回 IPv4 地址,无 AAAA 记录,说明系统解析器未返回 IPv6 地址,`curl -4` 的成功不能用IPv6解释。 + +### DNS服务器 + +在尝试AI给出的几个建议之后,我也开始尝试修改容器内的DNS服务器,进行这个尝试是因为我发现,使用`podman run`启动容器时,容器内部使用的DNS服务器是10.3.9.4这一主机使用的DNS服务器,而在CI管线中启动的容器使用的DNS服务器是172.17.14.1这一容器网关。 + +于是在CI启动的容器中,我通过直接写入`/etc/resolv.conf`文件的方式修改DNS服务器为10.3.9.4,在修改之后,针对两个域名的网络请求就恢复了正常。 + +在将上述的新发现同步给AI之后,AI又给出了一些新的思路。 + +### c-ares 异步 DNS 解析器兼容性问题? + +- 某些编译版本的 `curl`(尤其是 Alpine、某些容器镜像)会链接 `c-ares` 库,**绕过 glibc 的 `getaddrinfo`**,直接读取 `/etc/resolv.conf` 并发 DNS 请求。 +- `c-ares` **不读取 `/etc/nsswitch.conf`**,也不支持 mDNS、systemd-resolved 等机制。 +- 如果 `/etc/resolv.conf` 中的 nameserver(如 `172.17.14.1`)对 **某些域名响应异常**(如超时、截断、不响应 UDP 包),`c-ares` 会卡住。 + +但是按照AI提供的步骤进行排查: + +- `curl --version` 输出中**未包含 `c-ares`**; +- curl 使用 glibc 的 `getaddrinfo()` 或 libcurl threaded resolver; +- **结论**:非 c-ares 导致。 + +### UDP DNS 响应截断(>512 字节)? + +- `c-ares` 默认使用 **UDP** 查询 DNS。 +- 如果 Podman 的内置 DNS 代理(通常是 `netavark` + `aardvark-dns` 或旧版 `dnsmasq`)在处理 **特定域名** 的 UDP 请求时: + - 响应过大导致 UDP 截断(TC=1),但 `c-ares` 未自动重试 TCP; + - 或代理本身 bug 导致丢包; +- 则 `c-ares` 会认为“无响应”,返回“missing”。 + +而 `dig` 默认使用 UDP,但**当 TC=1时会自动使用TCP重试**,所以成功,而你的域名 `rcj.bupt-hpc.cn` 可能有较多 CNAME 或 TXT 记录,导致 UDP 响应超 512 字节。 + +但是我清楚的知道域名 `rcj.bupt-hpc.cn` 仅有一条 A 记录,DNS 响应极小,`dig` 与 `getent` 均能快速返回结果。 + +## AI Conclusion:Podman 内置 DNS 代理(aardvark-dns)行为异常 + +Podman 4.5.2 默认使用 **aardvark-dns** 作为容器网络的 DNS 代理,监听于网关地址(如 `172.10.0.1`)。其职责包括: + +- 解析容器名称(服务发现); +- 转发外部域名查询至上层 DNS。 + +可能的故障点包括: + +1. aardvark-dns 对特定域名后缀(如 `.bupt-hpc.cn`)的转发逻辑存在缺陷; +2. 响应格式不符合 glibc 或 libcurl 的预期(如缺少 AA 标志、TTL 异常); +3. 首次查询存在延迟或丢包,而 libcurl threaded resolver 未充分重试; +4. 与宿主机 `/etc/resolv.conf` 中的 `search` 域交互异常,导致拼接错误查询。 + +尽管 `dig` 能获取结果,但 `dig` 具有更强的容错性和自动重试机制(如 TC=1 时切 TCP),而 libcurl 的 resolver 更严格。 + +> 对于AI的这个结果,说实话我表示怀疑,我感觉还是和IPv6有关系,这样才能同时解释`curl -4`和更换DNS都可以运行的现象。 + +## 临时缓解 + +最终,我在CI启动容器的配置选项中添加`--dns 10.3.9.4`,不使用Podman启动的DNS服务器,暂时绕过了这个问题。 + +不过这个问题并没有从本质上得到解决,甚至都还不知道背后的具体问题是什么,感觉会在后面攻击我,特此记录。 diff --git a/YaeBlog/source/posts/program-design-introduction.md b/source/posts/program-design-introduction.md similarity index 59% rename from YaeBlog/source/posts/program-design-introduction.md rename to source/posts/program-design-introduction.md index 1171fdd..7dabd70 100644 --- a/YaeBlog/source/posts/program-design-introduction.md +++ b/source/posts/program-design-introduction.md @@ -1,18 +1,19 @@ --- title: 程序设计与计算导论笔记 -date: 2021-11-17 16:38:30 +date: 2021-11-17T16:38:30.0000000 tags: - - 学习资料 +- 学习资料 --- + 直接扒的老黄的PPT ,简直毫无参考价值。 不保证笔记的绝对正确性。 如果导致考试爆炸,不承担任何责任。 ## 第一章——程序设计概论 -![第一章](./program-design-introduction/1.png) +![第一章](./program-design-introduction/1.webp) ## 第二章——C语言概述 -![第二章](./program-design-introduction/2.png) -![第二章](./program-design-introduction/3.png) +![第二章](./program-design-introduction/2.webp) +![第二章](./program-design-introduction/3.webp) diff --git a/source/posts/program-design-introduction/1.webp b/source/posts/program-design-introduction/1.webp new file mode 100644 index 0000000..689fc76 --- /dev/null +++ b/source/posts/program-design-introduction/1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d57d00ba1d8fb8ba0d8e5fa0016eeaac4e270f927b1bcfcc2a2c9ac464fba53f +size 359910 diff --git a/source/posts/program-design-introduction/2.webp b/source/posts/program-design-introduction/2.webp new file mode 100644 index 0000000..f4bef91 --- /dev/null +++ b/source/posts/program-design-introduction/2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a4836148c40f2ba7129eadbb48fd372e2fd316c343157fe08c41f55113d3840 +size 420884 diff --git a/source/posts/program-design-introduction/3.webp b/source/posts/program-design-introduction/3.webp new file mode 100644 index 0000000..c5f86f3 --- /dev/null +++ b/source/posts/program-design-introduction/3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84d3936b5beb8b410df70a7e4e169451c859305ef97bc2214a5433fb01f2db59 +size 922942 diff --git a/YaeBlog/source/posts/qt-learning.md b/source/posts/qt-learning.md similarity index 98% rename from YaeBlog/source/posts/qt-learning.md rename to source/posts/qt-learning.md index c18cf5e..15e380b 100644 --- a/YaeBlog/source/posts/qt-learning.md +++ b/source/posts/qt-learning.md @@ -1,12 +1,12 @@ --- title: 初学Qt的一点小笔记 +date: 2022-07-01T14:32:39.0000000 tags: - - 技术笔记 - - C/C++ -typora-root-url: qt-learning -date: 2022-07-01 14:32:39 +- 技术笔记 +- C/C++ --- + 最近的大作业需要用 `C/C++`的技术栈实现一个图形化界面,`Qt`作为C++图形化框架久负盛名,正好借着这个写大作业的机会学习一下这个应用广泛的框架。 @@ -45,7 +45,7 @@ errorMsg.obj : error LNK2001: unresolved external symbol "public: virtual int __ - 在`CMakeLists.txt`文件中需要定义`set(CMAKE_AUTOMOC ON)` - 在包含这个头文件的地方需要将头文件的名称改为预处理之后的名称`moc_*.cpp`,如下图所示 -![](1.png) +![](1.webp) ## Qt Designer diff --git a/source/posts/qt-learning/1.webp b/source/posts/qt-learning/1.webp new file mode 100644 index 0000000..0fe27ab --- /dev/null +++ b/source/posts/qt-learning/1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ff8fa0f8cbfb3de6bfb98fb3d18ae4f5c9798c109d95ee14a5482755445ddd1 +size 27244 diff --git a/YaeBlog/source/posts/question-in-install-vs-2019.md b/source/posts/question-in-install-vs-2019.md similarity index 96% rename from YaeBlog/source/posts/question-in-install-vs-2019.md rename to source/posts/question-in-install-vs-2019.md index 4abbaf6..c11adcc 100644 --- a/YaeBlog/source/posts/question-in-install-vs-2019.md +++ b/source/posts/question-in-install-vs-2019.md @@ -1,10 +1,11 @@ --- title: 安装vs2019踩的坑 -date: 2021-08-14 10:06:00 +date: 2021-08-14T10:06:00.0000000 tags: - - 技术笔记 +- 技术笔记 --- + 在某个月黑风高的夜晚,我在折腾了很久的Python之后,突然感觉自己应该去学学C和C++,于是乎我便打算折腾一下在vscode上写C和C++。在网上一番搜寻之后,我发现了这篇[知乎文章](https://zhuanlan.zhihu.com/p/87864677)和这篇[知乎文章](https://zhuanlan.zhihu.com/p/147366852),然后我就被安装MinGW编译器和配置一大堆的json文件给干碎了。
于是,我决定转向传说中的宇宙第一IDE——Visual Studio。
@@ -12,7 +13,7 @@ tags: > 无他,就是太穷了。
在点击下载链接之后,跳转到了感谢下载的界面。
-![](./question-in-install-vs-2019/1.png) +![](./question-in-install-vs-2019/1.webp) 上面清楚的写着,下载将很快开始。 在经过漫长的等待之后,我下载下来了这个东西 >vs_community__355502915.1625217430.exe @@ -24,7 +25,7 @@ tags: >不可能,微软的软件不会出错,一定是我自己选错了。 我立刻卸载了电脑上的vs2017和Visual Studio Installer,再次前往vs的[官网](https://visualstudio.microsoft.com/zh-hans/vs/),这次我前检查后检查,确定自己下载的是Visual Studio 2019的Community版本,在单击了下载链接之后,我又跳转到了感谢下载的界面。 -![](./question-in-install-vs-2019/1.png) +![](./question-in-install-vs-2019/1.webp) 但是这次,在令人十分无语的等待之后,我不得不点了下单击此处以重试。 在一段令人紧张的等待之后,Visual Studio Installer安装好了,我也失望的发现,我安装的仍然是vs2017。 >谢谢,电脑已经砸了。 diff --git a/source/posts/question-in-install-vs-2019/1.webp b/source/posts/question-in-install-vs-2019/1.webp new file mode 100644 index 0000000..ccea748 --- /dev/null +++ b/source/posts/question-in-install-vs-2019/1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8541c37d3a7edfe50f6e8c4cb0d2ef8a5b860a9ae95e2d2adcfaa3326463f3ec +size 96866 diff --git a/YaeBlog/source/posts/rust-drop-stack-overflow.md b/source/posts/rust-drop-stack-overflow.md similarity index 98% rename from YaeBlog/source/posts/rust-drop-stack-overflow.md rename to source/posts/rust-drop-stack-overflow.md index 37927aa..abfded7 100644 --- a/YaeBlog/source/posts/rust-drop-stack-overflow.md +++ b/source/posts/rust-drop-stack-overflow.md @@ -6,6 +6,7 @@ tags: - 技术笔记 --- + 这辈子就是被Rust编译器害了.jpg @@ -14,15 +15,15 @@ tags: 事情是这样的,当我实现完`Sysy`语言的语法分析器并编写了一些白盒测试用例之后,我便打算将官方提供的100个测试用例作为输入运行看看能不能**正常**的解析成抽象语法树(显然不可能手动检查生成的抽象语法树是否正确)。我首先在`main.rs`里面实现了读取所有的`.sy`文件,进行词法分析和语法分析的逻辑,程序在这里这正常的识别了大多数的输入文件,在一些浮点数的输入上还存在一些问题。于是我便打算将这些逻辑重构到一个Rust的集成测试中,方便在CI中使用`cargo test`进行运行测试。但是在重构完成之后使用`cargo test`进行运行时我去遇到了如下的运行时错误。 -![image-20241105181144993](./rust-drop-stack-overflow/image-20241105181144993.png) +![image-20241105181144993](./rust-drop-stack-overflow/image-20241105181144993.webp) 看到这个报错的第一瞬间,我怀疑是因为`cargo test`和`cargo run`的运行环境不同,导致测试程序读取到了其他其实不是`sysy`程序但是以`.sy`结尾的文件,而恰好这个文件又能被解析,使得解析器组合子工作的过程中调用链太长而导致栈溢出,于是我在`RustRover`中打断点调试运行,却发现程序正确的读取到输入文件。这就奇怪了,我于是让程序继续运行到报错,看看报错时候程序的调用栈是被什么东西填满了,然后发现程序的调用栈长这样: -![image-20241105181612954](./rust-drop-stack-overflow/image-20241105181612954.png) +![image-20241105181612954](./rust-drop-stack-overflow/image-20241105181612954.webp) 并不是我程序中代码的调用太深导致的,而是Rust编译器自动生成的`drop`函数导致的。于是尝试看看调用栈的底部,看看是在读取什么输入数据,`drop`什么神仙数据结构的时候发生的。调试器很快告诉我们,`drop`的数据结构是抽象语法树中的二元表达式,而此时的输入代码则如下图所示,而且图中的代码重复了400行。 -![image-20241105182036975](./rust-drop-stack-overflow/image-20241105182036975.png) +![image-20241105182036975](./rust-drop-stack-overflow/image-20241105182036975.webp) 我已经能想象到那棵高耸如云的抽象语法树了。 diff --git a/source/posts/rust-drop-stack-overflow/image-20241105181144993.webp b/source/posts/rust-drop-stack-overflow/image-20241105181144993.webp new file mode 100644 index 0000000..c73fb82 --- /dev/null +++ b/source/posts/rust-drop-stack-overflow/image-20241105181144993.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47978418a849a3b60591de273a4041d739ef69de949af21d6a395151c7cfdff8 +size 12372 diff --git a/source/posts/rust-drop-stack-overflow/image-20241105181612954.webp b/source/posts/rust-drop-stack-overflow/image-20241105181612954.webp new file mode 100644 index 0000000..aea52eb --- /dev/null +++ b/source/posts/rust-drop-stack-overflow/image-20241105181612954.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f7c19288ab5cd8430c88796535dc598b5650939c3aab306c1f0b93cac716c99 +size 24398 diff --git a/source/posts/rust-drop-stack-overflow/image-20241105182036975.webp b/source/posts/rust-drop-stack-overflow/image-20241105182036975.webp new file mode 100644 index 0000000..4f45fda --- /dev/null +++ b/source/posts/rust-drop-stack-overflow/image-20241105182036975.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a46c711a6735d5581ff208b4fa2943622bb9ea1bfb9e8bd06f52b27e321cf479 +size 8022 diff --git a/YaeBlog/source/posts/rust-up-trait.md b/source/posts/rust-up-trait.md similarity index 100% rename from YaeBlog/source/posts/rust-up-trait.md rename to source/posts/rust-up-trait.md diff --git a/YaeBlog/source/posts/software-engineer.md b/source/posts/software-engineer.md similarity index 98% rename from YaeBlog/source/posts/software-engineer.md rename to source/posts/software-engineer.md index a5b0229..ddcd614 100644 --- a/YaeBlog/source/posts/software-engineer.md +++ b/source/posts/software-engineer.md @@ -1,11 +1,12 @@ --- title: 软件工程 +date: 2024-06-12T20:27:25.0000000 tags: - - 软件工程 - - 学习资料 -date: 2024-06-12 20:27:25 +- 软件工程 +- 学习资料 --- + 你说得对,但软工无非就是瀑布模型规定的那六项基本活动及其变化,然后细化到需求分析需要用UML规范化表示的建模方法,后面就是如何通过框架模块的结构确定软件对象有哪些并分配功能。当然这其中也涉及到模块独立性的设计原则以及面向对象的7个设计原则,之后就是软件测试的白盒和黑盒方法。 ## 软件工程概述 @@ -61,7 +62,7 @@ F.L. Bauer提出,软件工程就是为了经济地获得能够在实际机器 #### 瀑布模型 -![image-20240620211321957](software-engineer/image-20240620211321957.png) +![image-20240620211321957](software-engineer/image-20240620211321957.webp) 瀑布模型强调在软件实现之前必须进行分析和设计工作,同时也造成了模型缺乏灵活性的问题,特别是面对软件的需求不明确或者不准确的情况时,模型的风险控制能力较弱。 @@ -71,19 +72,19 @@ F.L. Bauer提出,软件工程就是为了经济地获得能够在实际机器 烟火模型提倡进行两次开发。第一次开发得到实验性的原型产品,其目的是探索可行性,弄清软件的需求。第二次在第一次的基础上获得较为满意的软件产品。 -![image-20240620212101864](software-engineer/image-20240620212101864.png) +![image-20240620212101864](software-engineer/image-20240620212101864.webp) 演化模型在明确用户需求和降低开发风险的同时,也带来了难以管理、结构较差和技术不成熟的问题,同时可能抛弃掉瀑布模型的文档控制优点。因此演化模型更多适合于需求不清晰的小型或中型系统,适用于开发周期较短的场合。 #### 增量模型 -![image-20240620214307906](software-engineer/image-20240620214307906.png) +![image-20240620214307906](software-engineer/image-20240620214307906.webp) 但是在增量模型中难以确定增量的粒度,同时确定所有的需求比较困难。 #### 喷泉模型 -![image-20240620214739548](software-engineer/image-20240620214739548.png) +![image-20240620214739548](software-engineer/image-20240620214739548.webp) 各个开发阶段没有特定的次序,可以并行进行,也可以在某个开发阶段中随时补充其他任何开发阶段。但是难以管理,工作计划需要随时更新。 @@ -91,11 +92,11 @@ F.L. Bauer提出,软件工程就是为了经济地获得能够在实际机器 将测试活动提前,使得瀑布模型能够驾驭风险。 -![image-20240620215022645](software-engineer/image-20240620215022645.png) +![image-20240620215022645](software-engineer/image-20240620215022645.webp) #### 螺旋模型 -![image-20240620220155962](software-engineer/image-20240620220155962.png) +![image-20240620220155962](software-engineer/image-20240620220155962.webp) 四个象限:制定计划、风险分析、实施工程、客户评价。 @@ -103,13 +104,13 @@ F.L. Bauer提出,软件工程就是为了经济地获得能够在实际机器 利用模块化思想将整个系统模块化,并在一定构件模型的支持下复用构件库中软件构件,通过组装高效率、高质量地构造软件系统。构件组装模型本质上是演化模型。 -![image-20240620224318982](software-engineer/image-20240620224318982.png) +![image-20240620224318982](software-engineer/image-20240620224318982.webp) 充分利用软件复用,提高开发效率。允许多个项目同时开发,降低了费用,提高了可维护性。但是缺乏通用的构件组装结构标准,风险较大,同时构件可重用性和系统高效性之间不易协调。 #### 快速应用开发模型 -![image-20240620224519243](software-engineer/image-20240620224519243.png) +![image-20240620224519243](software-engineer/image-20240620224519243.webp) 增量型的软件开发过程,强调极短的开发周期。 @@ -142,7 +143,7 @@ RUP由9个核心工作流构建每个迭代的主要活动。分别是6个核心 敏捷建模由一系列的价值观、原则和实践组成,是一种快速软件开发的思想代码,代表性的应用有极限编程。 -![image-20240621114540033](software-engineer/image-20240621114540033.png) +![image-20240621114540033](software-engineer/image-20240621114540033.webp) 极限编程中的需求分析要求开发人员和客户一起将各种需求变成小的需求模块(User Story),这些模块将会被组合或者分解为更小的模块,成为Story Card。 @@ -179,13 +180,13 @@ UML,即统一建模语言,是面向对象分析与设计的一种标准表 4+1视图,通过模型来描述系统的结构和行为,或者说静态结构和动态结构,以用例视图为核心描述系统的不同视图。 -![image-20240623160826903](software-engineer/image-20240623160826903.png) +![image-20240623160826903](software-engineer/image-20240623160826903.webp) #### 领域模型 领域模型是针对某一个特定领域内概念类或者对象的抽象化可视化表示。 -![image-20240623162229404](software-engineer/image-20240623162229404.png) +![image-20240623162229404](software-engineer/image-20240623162229404.webp) 在领域模型中使用类图表达概念及其关系,概念类之间的关系有关联、继承、组合、聚合和依赖等关系。 @@ -203,11 +204,11 @@ UML,即统一建模语言,是面向对象分析与设计的一种标准表 模块的内聚性是模块功能强度的度量。 -![image-20240623163647935](software-engineer/image-20240623163647935.png) +![image-20240623163647935](software-engineer/image-20240623163647935.webp) 模块的耦合性是针对模块之间连接紧密程度的度量。 -![image-20240623170058679](software-engineer/image-20240623170058679.png) +![image-20240623170058679](software-engineer/image-20240623170058679.webp) 面向对象的设计原则: diff --git a/source/posts/software-engineer/image-20240620211321957.webp b/source/posts/software-engineer/image-20240620211321957.webp new file mode 100644 index 0000000..87d088e --- /dev/null +++ b/source/posts/software-engineer/image-20240620211321957.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b2697e6eb8393a0d6fc9a7b7a0c5a8b6fcf1eaa41bff77a797d76b966159fc5 +size 21830 diff --git a/source/posts/software-engineer/image-20240620212101864.webp b/source/posts/software-engineer/image-20240620212101864.webp new file mode 100644 index 0000000..38bd965 --- /dev/null +++ b/source/posts/software-engineer/image-20240620212101864.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97fa916111246230e28cfc64fcf6fbba2afc2e00924198f2fda575d37a84fda2 +size 30260 diff --git a/source/posts/software-engineer/image-20240620214307906.webp b/source/posts/software-engineer/image-20240620214307906.webp new file mode 100644 index 0000000..40b2249 --- /dev/null +++ b/source/posts/software-engineer/image-20240620214307906.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a4bbaf9c565fde3742a8dd345596ff6abe38fa116233cd3fc96702e48f9f858 +size 44954 diff --git a/source/posts/software-engineer/image-20240620214739548.webp b/source/posts/software-engineer/image-20240620214739548.webp new file mode 100644 index 0000000..a859714 --- /dev/null +++ b/source/posts/software-engineer/image-20240620214739548.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5297367eab21bfd75cfa6344fa0f5f7d0dcc8bef5223216ab9542020f5a01af3 +size 50498 diff --git a/source/posts/software-engineer/image-20240620215022645.webp b/source/posts/software-engineer/image-20240620215022645.webp new file mode 100644 index 0000000..a75f89e --- /dev/null +++ b/source/posts/software-engineer/image-20240620215022645.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d46328f2e4f3e91dbcaaa880f7b02d847254668a481c301ea4ba04718d12b88 +size 63100 diff --git a/source/posts/software-engineer/image-20240620220155962.webp b/source/posts/software-engineer/image-20240620220155962.webp new file mode 100644 index 0000000..93f9a1b --- /dev/null +++ b/source/posts/software-engineer/image-20240620220155962.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e232c96e9826a39a2123d6048de17b6d3c1d911f63c03c7618c5454e0621588e +size 51084 diff --git a/source/posts/software-engineer/image-20240620224318982.webp b/source/posts/software-engineer/image-20240620224318982.webp new file mode 100644 index 0000000..dcb50de --- /dev/null +++ b/source/posts/software-engineer/image-20240620224318982.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84698ab4aacdb986e6f74a30f95e894e041821f40401f77ce67b11a57528bcbf +size 63542 diff --git a/source/posts/software-engineer/image-20240620224519243.webp b/source/posts/software-engineer/image-20240620224519243.webp new file mode 100644 index 0000000..70d5e47 --- /dev/null +++ b/source/posts/software-engineer/image-20240620224519243.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5d2cc95f6aea29c53f517a968a4a21f69cd10c9b8e2a350ee6d3bb4a21a1754 +size 26200 diff --git a/source/posts/software-engineer/image-20240621114540033.webp b/source/posts/software-engineer/image-20240621114540033.webp new file mode 100644 index 0000000..d3e1976 --- /dev/null +++ b/source/posts/software-engineer/image-20240621114540033.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6711902df854e2b52b811a2669e5d1257d4dec0b05e9fcacff98ea15f8b0cc62 +size 37034 diff --git a/source/posts/software-engineer/image-20240623160826903.webp b/source/posts/software-engineer/image-20240623160826903.webp new file mode 100644 index 0000000..3f0ba5e --- /dev/null +++ b/source/posts/software-engineer/image-20240623160826903.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:719075736d61689f9a62b5fc4726ab1a5fed84c95513a7946745b7fbce7b001f +size 25908 diff --git a/source/posts/software-engineer/image-20240623162229404.webp b/source/posts/software-engineer/image-20240623162229404.webp new file mode 100644 index 0000000..3b1b77f --- /dev/null +++ b/source/posts/software-engineer/image-20240623162229404.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a6204468dafff77a9498f44d972cd743b109aa4c62618325b83c8bd94714eb6 +size 40468 diff --git a/source/posts/software-engineer/image-20240623163647935.webp b/source/posts/software-engineer/image-20240623163647935.webp new file mode 100644 index 0000000..23be7f8 --- /dev/null +++ b/source/posts/software-engineer/image-20240623163647935.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e3cf05b12b962dd28b0c2cf4d400d7f9b570eb5221477d0760c57d797986e5b +size 25066 diff --git a/source/posts/software-engineer/image-20240623170058679.webp b/source/posts/software-engineer/image-20240623170058679.webp new file mode 100644 index 0000000..b774fc4 --- /dev/null +++ b/source/posts/software-engineer/image-20240623170058679.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0602498704ee426ed4cc6458e3812ba19adde20ecaac9c5e0ce6cb757fb16a2c +size 58316 diff --git a/YaeBlog/source/posts/spring-boot-custom-authorize.md b/source/posts/spring-boot-custom-authorize.md similarity index 99% rename from YaeBlog/source/posts/spring-boot-custom-authorize.md rename to source/posts/spring-boot-custom-authorize.md index 85736d1..c4d1a63 100644 --- a/YaeBlog/source/posts/spring-boot-custom-authorize.md +++ b/source/posts/spring-boot-custom-authorize.md @@ -1,11 +1,12 @@ --- title: SpringBoot自定义注解实现权限控制 +date: 2023-07-29T15:20:02.0000000 tags: - - 技术笔记 - - Java -date: 2023-07-29 15:20:02 +- 技术笔记 +- Java --- + 如题。 @@ -131,13 +132,13 @@ public interface AuthorizeService { 然后针对每个策略实现一个认证服务: -![image-20230727175807814](spring-boot-custom-authorize/image-20230727175807814.png) +![image-20230727175807814](spring-boot-custom-authorize/image-20230727175807814.webp) > 具体实现就不在这里给出。 在注入每个服务时,使用策略枚举作为服务的名称,方便后续获得该服务实例。 -![image-20230727175955817](spring-boot-custom-authorize/image-20230727175955817.png) +![image-20230727175955817](spring-boot-custom-authorize/image-20230727175955817.webp) ### 注解和注解解析 diff --git a/source/posts/spring-boot-custom-authorize/image-20230727175807814.webp b/source/posts/spring-boot-custom-authorize/image-20230727175807814.webp new file mode 100644 index 0000000..7b5cec1 --- /dev/null +++ b/source/posts/spring-boot-custom-authorize/image-20230727175807814.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eea0b3896b8d71c1f795b193cf262851e2d5e1930ba977b5bec070dc1e3f5146 +size 6198 diff --git a/source/posts/spring-boot-custom-authorize/image-20230727175955817.webp b/source/posts/spring-boot-custom-authorize/image-20230727175955817.webp new file mode 100644 index 0000000..45f3263 --- /dev/null +++ b/source/posts/spring-boot-custom-authorize/image-20230727175955817.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1953b390215fa766bd500b202b403ac9e20578c7f85911055d207ea06aa424b +size 17224 diff --git a/source/posts/system-text-json.md b/source/posts/system-text-json.md new file mode 100644 index 0000000..d0e3203 --- /dev/null +++ b/source/posts/system-text-json.md @@ -0,0 +1,344 @@ +--- +title: 使用System.Text.Json序列化和反序列化JSON +date: 2026-01-21T22:07:38.4297603+08:00 +updateTime: 2026-01-21T22:07:38.4370636+08:00 +tags: +- 技术笔记 +- dotnet +--- + + + +如何使用`System.Text.Json`高效地序列化和反序列化JSON? + + + +### 序列化 + +序列化JSON几乎总是简单的,直接使用`JsonSerializer.Serialize`就可以序列化为字符串。 + +唯一需要注意的是,JSON理论上唯一的数字类型`number`默认是双精度浮点数,只能**精确地**表示53位(二进制)以下的整数。在对于`long`类型进行序列化时,虽然框架可以输出正确的数值,但是JavaScript中无法正确的解析。 + +```csharp + [Fact] + public void LongSerializeTest() + { + JsonBody body = new(long.MaxValue - 1); + string output = JsonSerializer.Serialize(body); + // Output: {"Number":9223372036854775806} + outputHelper.WriteLine(output); + } +``` + +上述的JSON字符串中在JavaScript中将会被解析为: + +![image-20260120153508775](./system-text-json/image-20260120153508775.webp) + +因此在需要传递大整数的时候最好使用`String`。 + +### 反序列化 + +而反序列化中需要考虑的东西就很多了。 + +#### 使用记录声明反序列化的对象 + +在`System.Text.Json`的早期版本中,无法将JSON反序列化为`record`这类关键词声明的不可变类型,因为当时库的逻辑是首先调用类型的公共无参数构造函数构造对象,再使用setter为需要反序列化的属性赋值。在后来的版本中,序列化程序可以直接调用类型的构造函数进行反序列化,这就为反序列化到`record`和`struct`提供了方便。 + +例如可以使用如下的代码快速地进行反序列化: + +```csharp + private record JsonBody(int Code, string Username); + + [Fact] + public void DeserializeTest() + { + const string input = """ + { + "code": 111, + "username": "ricardo" + } + """; + + JsonBody? body = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.NotNull(body); + + Assert.Equal(111, body.Code); + Assert.Equal("ricardo", body.Username); + } +``` + +但是这样进行反序列化有一个小小的坑,就是缺少对于空值的有效处理。例如对于下面的JSON,上面的代码都会正常地进行反序列化。 + +```csharp + [Fact] + public void DeserializeFromNonexistFieldTest() + { + const string input = """ + { + "code": 111 + } + """; + + JsonBody? body = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.NotNull(body); + + Assert.Equal(111, body.Code); + Assert.Equal("", body.Username); + } +``` + +```csharp + [Fact] + public void DeserializeFromNullValueTest() + { + const string input = """ + { + "code": 111, + "username": null + } + """; + + JsonBody? body = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.NotNull(body); + + Assert.Equal(111, body.Code); + Assert.Equal("", body.Username); + } +``` + +但是对于返回结果的校验会发现`body.Username`实际上是一个空值。 + +![image-20260121221219618](./system-text-json/image-20260121221219618.webp) + +幸好,在.NET 9中为`JsonSerializerOptions`添加了一个尊重可为空注释的选项`RespectNullableAnnotations`,将这个选项设置为`true`可以在**一定程度上**缓解这个问题。打开这个开关之后,对于`"username": null`的反序列化就会抛出异常了。 + +但是针对第一段JSON,也就是缺少了`username`字段的反序列化并不会报错,这就是反序列化的第二个坑,这里先按下不表。 + +因为在.NET运行时的设计初期并没有考虑空安全这一至关重要的特性,因此在IL中并没有针对引用类型的不可为空性的显式抽象(虽然后续的C#编译器会为所有不可为空的应用类型添加属性元数据)。所以,针对如下元素的不可为空约束是无效的: + +1. 顶级类型; +2. 集合的元素类型; +3. 任何含有泛型的属性、字段和构造函数参数。 + +例如,针对下面这个反序列化代码并不会报错,需要程序员自行处理其中的空值: + +```csharp + [Fact] + public void DeserializeListTest() + { + const string input = """ + { + "names": [ + "1", + null, + "2" + ] + } + """; + + JsonListBody? body = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.NotNull(body); + + foreach ((int i, string value) in body.Names.Index()) + { + outputHelper.WriteLine($"{i} is null? {value is null}"); + } + } +``` + +运行的输出结果提示第二个元素为空: + +![image-20260120172747047](./system-text-json/image-20260120172747047.webp) + +#### 需要才是需要,不为空并不一定不为空 + +在默认的反序列化行为中,如果反序列化对象的某一个属性并不在输入的JSON对象中,反序列化器并不为报错而是直接设置为null,这显然会给破环空安全的假定,即使打开了尊重空值注释也是这样。这在.NET文档中被称为**缺失值和空值**: + +- **显式空值null**将会在`RespectNullableAnnontations=true`的情况下引发异常; +- **缺少的属性**不会引发任何异常,即使对应的属性被声明为不可为空。 + +为了让序列化程序确保缺少属性时会报错,需要将这个属性声明为**需要的**。这一点可以通过C#的`required`关键词或者`[Required]`属性来实现。 + +而且,这两种属性对于C#语言和序列化程序来说是正交的,即: + +1. 可以有一个可以为空的必需属性: + + ```csharp + MyPoco poco = new() { Value = null }; // No compiler warnings. + + class MyPoco + { + public required string? Value { get; set; } + } + ``` + +2. 可以有一个不可为空的可选属性: + + ```csharp + class MyPoco + { + public string Value { get; set; } = "default"; + } + ``` + +但是对于`record`类型来说,前者在语义上是冗余的,语法上是错误的,后者则对于程序员带来了额外的心智负担,需要手动给每一个字段加上一个额外的注解。 + +考虑到序列化程序也支持使用有参数的公共构造函数,上面这两个属性对于构造函数的参数来说也是成立的: + +```csharp +record MyPoco( + string RequiredNonNullable, + string? RequiredNullable, + string OptionalNonNullable = "default", + string? OptionalNullable = "default" + ); +``` + +不过在.NET 9之前,所有构造函数的参数都被序列化程序认为是可选的。在.NET 9之后,`JsonSerializerOptions`添加了一个尊重必须构造函数参数的选项(别忘了对于`record`这类不可变对象的反序列化是通过构造函数来实现的)`RespectRequiredConstructorParameters`。在打开这个选项之后,针对缺少属性的反序列化就会正常报错了。 + +```csharp + private static readonly JsonSerializerOptions s_serializerOptions = new() + { + PropertyNameCaseInsensitive = true, + RespectNullableAnnotations = true, + RespectRequiredConstructorParameters = true + }; + + [Fact] + public void DeserializeFromNonexistFieldTest() + { + const string input = """ + { + "code": 111 + } + """; + + Assert.Throws(() => JsonSerializer.Deserialize(input, s_serializerOptions)); + } +``` + +#### 反序列化为结构 + +结构作为值类型,虽然在函数之间传递时需要被拷贝而带来了额外的性能开销,但是也因为这一点而可以被直接分配在栈上,给GC带来的压力较小。因此在部分需要极端性能优化的场景可以直接针对`struct`进行反序列化。 + +`struct`的反序列化也是通过构造函数来实现的,序列化程序遵循如下的规则来选择构造函数: + +1. 对于类,如果唯一的构造函数是参数化构造函数,则选择这一构造函数; +2. 对于结构或者具有多个构造函数的类,需要使用`[JsonConstructor]`手动指定需要使用的构造函数,否则**只会**使用公共无参构造函数(如果存在)。 + +因此,如果需要针对不可变的结构进行反序列化,需要加上`[JsonConstructor]`注解。例如,针对下面的代码,如果不加上注解,反序列化又会静默地失败。 + +```csharp + private struct JsonStruct + { + public int Id { get; } + + public string Name { get; } + + [JsonConstructor] + public JsonStruct(int id, string name) + { + Id = id; + Name = name; + } + } + + [Fact] + public void DeserializeToStructTest() + { + const string input = """ + { + "Id": 1, + "Name": "ricardo" + } + """; + + JsonStruct r = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.Equal(1, r.Id); + } +``` + +为了简化语法,不可变的结构可以使用`readonly record struct`语法来替代: + +```csharp + private readonly record struct JsonRecordStruct(int Id, string Name); + + [Fact] + public void DeserializeToRecordStructTest() + { + const string input = """ + { + "Id": 1, + "Name": "ricardo" + } + """; + + JsonRecordStruct r = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.Equal(1, r.Id); + Assert.Equal("ricardo", r.Name); + } +``` + +不过这里有一个很奇怪的点,使用`readonly record struct`语法之后就不需要`[JsonConstructor]`了。 + +可以实验一下是`readonly`还是`record`发挥了作用。 + +在仅仅添加了`readonly`的情况下,反序列化不会成功: + +```csharp + private readonly struct JsonReadonlyStruct + { + public int Id { get; } + + public string Name { get; } + + public JsonReadonlyStruct(int id, string name) + { + Id = id; + Name = name; + } + } + + [Fact] + public void DeserializeToReadonlyStructTest() + { + const string input = """ + { + "Id": 1, + "Name": "ricardo" + } + """; + + JsonReadonlyStruct r = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.Equal(0, r.Id); + Assert.Null(r.Name); + } +``` + +而在仅仅加上`record`的情况下,序列化程序就可以选择正确的构造函数了: + +```csharp + private record struct JsonRecordStruct(int Id, string Name); + + [Fact] + public void DeserializeToRecordStructTest() + { + const string input = """ + { + "Id": 1, + "Name": "ricardo" + } + """; + + JsonRecordStruct r = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.Equal(1, r.Id); + Assert.Equal("ricardo", r.Name); + } +``` + +> 不过这样说来`readonly record struct`中的`readonly`似乎是冗余的? +> +> 原来,`record struct`声明的对象是可变的。详见文档中对于[不可变性](https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/builtin-types/record#immutability)的描述。 + + + diff --git a/source/posts/system-text-json/image-20260120153508775.webp b/source/posts/system-text-json/image-20260120153508775.webp new file mode 100644 index 0000000..8ad116a --- /dev/null +++ b/source/posts/system-text-json/image-20260120153508775.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9208f2f64897c301ff04ee909f62c4d1dd043b4cf1d64fad8cd211f5a569b66c +size 9250 diff --git a/source/posts/system-text-json/image-20260120172747047.webp b/source/posts/system-text-json/image-20260120172747047.webp new file mode 100644 index 0000000..f7aa060 --- /dev/null +++ b/source/posts/system-text-json/image-20260120172747047.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:601e969b03143af307eaf71d165bcd230685856e27a5839362aad7eac58de530 +size 3456 diff --git a/source/posts/system-text-json/image-20260121221219618.webp b/source/posts/system-text-json/image-20260121221219618.webp new file mode 100644 index 0000000..9f8656a --- /dev/null +++ b/source/posts/system-text-json/image-20260121221219618.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:272013474d78d6a770eaa3d0cf7c11785458591fa77ed887d5d1dfbf9809ee46 +size 3440 diff --git a/YaeBlog/source/posts/update-windows-break-archlinux.md b/source/posts/update-windows-break-archlinux.md similarity index 100% rename from YaeBlog/source/posts/update-windows-break-archlinux.md rename to source/posts/update-windows-break-archlinux.md diff --git a/YaeBlog/source/posts/using-vpn-elegant.md b/source/posts/using-vpn-elegant.md similarity index 100% rename from YaeBlog/source/posts/using-vpn-elegant.md rename to source/posts/using-vpn-elegant.md diff --git a/YaeBlog/source/posts/vscode-in-browser.md b/source/posts/vscode-in-browser.md similarity index 97% rename from YaeBlog/source/posts/vscode-in-browser.md rename to source/posts/vscode-in-browser.md index a24b8af..842578c 100644 --- a/YaeBlog/source/posts/vscode-in-browser.md +++ b/source/posts/vscode-in-browser.md @@ -1,10 +1,11 @@ --- title: 在浏览器中打开VSCode -date: 2021-10-30 17:16:13 +date: 2021-10-30T17:16:13.0000000 tags: - - 技术笔记 +- 技术笔记 --- + 众所周知,VSCode作为大微软家开发的开源编辑器,一经发布便受到了两广大程序员群体的欢迎。如果我们深入的了解一下VSCode,就会知道VSCode是基于Electron框架构建的Web应用程序,而Electron框架是基于Web技术来开发桌面应用程序,即VSCode只要稍加改造,就可以流畅的在浏览器中运行。那么我们如何才能在浏览器打开一个VSCode呢? 最简单的方法是在浏览器中输入[这个网址](https://vscode.dev),就可以在浏览器中打开一个VSCode Online,这个版本的VSCode可以支持打开本地的文件,并且进行编辑。不过这个编辑器并不支持大部分的插件,而且并不支持程序的编译与运行,并不是一个可以开箱即用的编辑器。那么还有什么办法可以让我们拥有一个联网即可得的个人定制化开发环境呢? @@ -53,9 +54,9 @@ cert: false ``` 在第一行,127.0.0.1代表这是本机的IP,如果要在公网上访问的话,需要将这里的IP改为0.0.0.0,后面的端口在默认的情况下是8080,你可以改成自己喜欢的端口号,在第二行的password表示采用密码进行身份验证,我们需要在第三行设置自己熟悉的密码,以方便自己的访问,当然,你把默认生成的密码背下来应该也是可以的 在进行了这些更改之后,我们再次输入code-server重启服务,如果一次顺利,我们可以看见以下的启动信息 -![启动信息](./vscode-in-browser/1.png) +![启动信息](./vscode-in-browser/1.webp) 我们可以打开浏览器,在地址栏中输入你的服务器公网IP加上你自己设置的端口号,就可以打开自己的VSCode Online界面了。 -![主界面](./vscode-in-browser/2.png) +![主界面](./vscode-in-browser/2.webp) 输入自己的设置密码,就可以开始把浏览器中的VSCode当作自己本地计算机上的VSCode使用了,不过其中的文件是位于自己的服务器上的。 >如果你和我一样使用的阿里云的服务器,可能还需要到服务器的管理界面设置安全组放行相应的端口,具体参考[这篇文章](https://help.aliyun.com/document_detail/59086.html?spm=5176.10173289.help.dexternal.4ff02e77892BZP) diff --git a/source/posts/vscode-in-browser/1.webp b/source/posts/vscode-in-browser/1.webp new file mode 100644 index 0000000..630199c --- /dev/null +++ b/source/posts/vscode-in-browser/1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25d3c15f13764c382ec4bcaf3cbb4babec22de0ca7f8681c5e3065a7a358cf8a +size 7062 diff --git a/source/posts/vscode-in-browser/2.webp b/source/posts/vscode-in-browser/2.webp new file mode 100644 index 0000000..8a699d1 --- /dev/null +++ b/source/posts/vscode-in-browser/2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:26dd469996330f22d0c1bf26224fe6781d5ce0f05dea8d48ec28f3cc46563d0c +size 20430 diff --git a/YaeBlog/source/posts/whats-new-of-dotnet-8.md b/source/posts/whats-new-of-dotnet-8.md similarity index 99% rename from YaeBlog/source/posts/whats-new-of-dotnet-8.md rename to source/posts/whats-new-of-dotnet-8.md index d551565..2fe519e 100644 --- a/YaeBlog/source/posts/whats-new-of-dotnet-8.md +++ b/source/posts/whats-new-of-dotnet-8.md @@ -1,12 +1,13 @@ --- title: .Net 8中我关心的新功能 -date: 2023-11-22 21:07:03 +date: 2023-11-22T21:07:03.0000000 tags: - - 技术笔记 - - dotnet +- 技术笔记 +- dotnet --- + ## .NET 首先看看.NET 8的新增功能,参照官方文档中的[.NET 8的新增功能](https://learn.microsoft.com/zh-cn/dotnet/core/whats-new/dotnet-8)。 @@ -307,11 +308,11 @@ ab -n 10000 -c 1000 http://locallhost:5000/todos/ 测试之后的结果的如图所示: -![image-20231122100930849](./whats-new-of-dotnet-8/image-20231122100930849.png) +![image-20231122100930849](./whats-new-of-dotnet-8/image-20231122100930849.webp) 直接使用运行时运行编译出来的`dll`文件: -![image-20231122101012416](./whats-new-of-dotnet-8/image-20231122101012416.png) +![image-20231122101012416](./whats-new-of-dotnet-8/image-20231122101012416.webp) 修改项目文件和源代码,取消`System.Text.Json`使用源生成器和对于`AOT`的支持。 @@ -346,11 +347,11 @@ public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplet 使用同样的命令测试得到的结果如图: -![image-20231122101142080](./whats-new-of-dotnet-8/image-20231122101142080.png) +![image-20231122101142080](./whats-new-of-dotnet-8/image-20231122101142080.webp) 可以发现几次测试的结果都比较接近,几乎可以认为`AOT`编译不会对性能产生明显的影响。虽然按照官方博客中给出的图片,`AOT`编译是有性能下降,应该是我这边测试工具的瓶颈。 -![Before and After AOT](./whats-new-of-dotnet-8/AOTOptimizations4.png) +![Before and After AOT](./whats-new-of-dotnet-8/AOTOptimizations4.webp) ### Identity API 终结点 diff --git a/source/posts/whats-new-of-dotnet-8/AOTOptimizations4.webp b/source/posts/whats-new-of-dotnet-8/AOTOptimizations4.webp new file mode 100644 index 0000000..b46ddc9 --- /dev/null +++ b/source/posts/whats-new-of-dotnet-8/AOTOptimizations4.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b440e741b2aa0a0d302a5137898cf909fe0999840e6776403d0ec71aec6a9a8 +size 74832 diff --git a/source/posts/whats-new-of-dotnet-8/image-20231122100930849.webp b/source/posts/whats-new-of-dotnet-8/image-20231122100930849.webp new file mode 100644 index 0000000..caaca4c --- /dev/null +++ b/source/posts/whats-new-of-dotnet-8/image-20231122100930849.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59b34d52384b2bdfd7b0e46a6d32cca1256eec56dbf3968eb9d35b174748bfb6 +size 13238 diff --git a/source/posts/whats-new-of-dotnet-8/image-20231122101012416.webp b/source/posts/whats-new-of-dotnet-8/image-20231122101012416.webp new file mode 100644 index 0000000..04c452f --- /dev/null +++ b/source/posts/whats-new-of-dotnet-8/image-20231122101012416.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40a5125c6bb0860ee004c404d03d843865020fbdbcf9bdcbc8aec743f7eb91be +size 12984 diff --git a/source/posts/whats-new-of-dotnet-8/image-20231122101142080.webp b/source/posts/whats-new-of-dotnet-8/image-20231122101142080.webp new file mode 100644 index 0000000..a71188a --- /dev/null +++ b/source/posts/whats-new-of-dotnet-8/image-20231122101142080.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72e444663b11307e6611b134f6133e5c3a918b390703542b509cf518ce4352e5 +size 13014 diff --git a/YaeBlog/source/posts/wsl-setup-csapp.md b/source/posts/wsl-setup-csapp.md similarity index 98% rename from YaeBlog/source/posts/wsl-setup-csapp.md rename to source/posts/wsl-setup-csapp.md index 9b2c144..5641470 100644 --- a/YaeBlog/source/posts/wsl-setup-csapp.md +++ b/source/posts/wsl-setup-csapp.md @@ -1,12 +1,12 @@ --- title: 利用WSL设置CSAPP实验环境 +date: 2022-09-03T19:02:58.0000000 tags: - - 技术笔记 - - Linux -typora-root-url: wsl-setup-csapp -date: 2022-09-03 19:02:58 +- 技术笔记 +- Linux --- + `CSAPP`这本书为自学的学生们提供了不少的`LAB`供大家在联系中提高,但是这些`LAB`的编写普遍需要一个`Linux`的实验环境,但是目前大多数人手中的环境都是`Windows`平台,没有办法原生的运行这些`LAB`。在以前的实践中,这个问题往往是通过安装虚拟机来解决的,但是现在我们有了更好的解决方案——`Windows Subsystem for Linux`,简称`WSL`。 @@ -29,7 +29,7 @@ wsl --install 回车,等待所有的进度条都走完,重新启动系统。 -![设置的画面](1.png) +![设置的画面](1.webp) 在重启完成之后,Ubuntu系统会自动启动并完成一系列的配置,在其中需要你为这个系统设置一个用户。输入这个用户的名称和密码即可。 @@ -59,7 +59,7 @@ sudo apt update 测试换源是否成功。 -![证书错误](2.png) +![证书错误](2.webp) 如果在换源的过程中报错`Certificate verification failed`,可以将配置文件中的所有`https`更改为`http`来临时解决。 @@ -160,7 +160,7 @@ gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1) 如果遇到报错,部分头文件未找到: -![Error](3.png) +![Error](3.webp) 不要慌张,这是正常的。 diff --git a/source/posts/wsl-setup-csapp/1.webp b/source/posts/wsl-setup-csapp/1.webp new file mode 100644 index 0000000..63f928a --- /dev/null +++ b/source/posts/wsl-setup-csapp/1.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa53cc3c0ea32b6eb58619436b78a3fc786498b8723fc27d350dd1b3f986a5f4 +size 27920 diff --git a/source/posts/wsl-setup-csapp/2.webp b/source/posts/wsl-setup-csapp/2.webp new file mode 100644 index 0000000..caf9c78 --- /dev/null +++ b/source/posts/wsl-setup-csapp/2.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c860be85e00aa27a9b776f9ea8041b64b514113adbad53057a22e4a2f81f8af5 +size 182264 diff --git a/source/posts/wsl-setup-csapp/3.webp b/source/posts/wsl-setup-csapp/3.webp new file mode 100644 index 0000000..56967d9 --- /dev/null +++ b/source/posts/wsl-setup-csapp/3.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53d6b2e66cec1cc60f0c194e09d05ec987eea42527c554e52496366deccfb618 +size 18364 diff --git a/src/YaeBlog.Tests/DateTimeOffsetTests.cs b/src/YaeBlog.Tests/DateTimeOffsetTests.cs new file mode 100644 index 0000000..f9fd8db --- /dev/null +++ b/src/YaeBlog.Tests/DateTimeOffsetTests.cs @@ -0,0 +1,13 @@ +namespace YaeBlog.Tests; + +public class DateTimeOffsetTests +{ + [Fact] + public void DateTimeOffsetParseTest() + { + const string input = "2026-01-04T16:36:36.5629759+08:00"; + DateTimeOffset time = DateTimeOffset.Parse(input); + + Assert.Equal("2026年01月04日 16:36:36", time.ToString("yyyy年MM月dd日 HH:mm:ss")); + } +} diff --git a/src/YaeBlog.Tests/GiteaFetchServiceTests.cs b/src/YaeBlog.Tests/GiteaFetchServiceTests.cs new file mode 100644 index 0000000..0adfd31 --- /dev/null +++ b/src/YaeBlog.Tests/GiteaFetchServiceTests.cs @@ -0,0 +1,38 @@ +using DotNext; +using Microsoft.Extensions.Options; +using Moq; +using YaeBlog.Models; +using YaeBlog.Services; + +namespace YaeBlog.Tests; + +public sealed class GiteaFetchServiceTests +{ + private static readonly Mock> s_giteaOptionsMock = new(); + private readonly GiteaFetchService _giteaFetchService; + + public GiteaFetchServiceTests() + { + s_giteaOptionsMock.SetupGet(o => o.Value) + .Returns(new GiteaOptions + { + BaseAddress = "https://git.rrricardo.top/api/v1/", + ApiKey = "7e33617e5d084199332fceec3e0cb04c6ddced55", + HeatMapUsername = "jackfiled" + }); + + _giteaFetchService = new GiteaFetchService(s_giteaOptionsMock.Object, new HttpClient()); + } + + [Fact] + public async Task FetchHeapMapTest() + { + Result> r = await _giteaFetchService.FetchGiteaContributions("jackfiled"); + + Assert.Null(r.Error); + Assert.True(r.TryGet(out List? items)); + Assert.NotNull(items); + + Assert.True(items.Count > 0); + } +} diff --git a/src/YaeBlog.Tests/JsonDeserializationTests.cs b/src/YaeBlog.Tests/JsonDeserializationTests.cs new file mode 100644 index 0000000..fdf369f --- /dev/null +++ b/src/YaeBlog.Tests/JsonDeserializationTests.cs @@ -0,0 +1,185 @@ +using System.Text.Json; +using Xunit.Abstractions; + +namespace YaeBlog.Tests; + +public sealed class JsonDeserializationTests(ITestOutputHelper outputHelper) +{ + private record JsonBody(int Code, string Username); + + private static readonly JsonSerializerOptions s_serializerOptions = new() + { + PropertyNameCaseInsensitive = true, + RespectNullableAnnotations = true, + RespectRequiredConstructorParameters = true + }; + + [Fact] + public void DeserializeTest() + { + const string input = """ + { + "code": 111, + "username": "ricardo" + } + """; + + JsonBody? body = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.NotNull(body); + + Assert.Equal(111, body.Code); + Assert.Equal("ricardo", body.Username); + } + + [Fact] + public void DeserializeFromNonexistFieldTest() + { + const string input = """ + { + "code": 111 + } + """; + + Assert.Throws(() => JsonSerializer.Deserialize(input, s_serializerOptions)); + } + + [Fact] + public void DeserializeFromNullValueTest() + { + const string input = """ + { + "code": 111, + "username": null + } + """; + + Assert.Throws(() => JsonSerializer.Deserialize(input, s_serializerOptions)); + } + + [Fact] + public void DeserializeFromUndefinedValueTest() + { + const string input = """ + { + "code": 111, + "username": undefined + } + """; + + Assert.Throws(() => JsonSerializer.Deserialize(input, s_serializerOptions)); + } + + private record JsonListBody(List Names); + + [Fact] + public void DeserializeListTest() + { + const string input = """ + { + "names": [ + "1", + null, + "2" + ] + } + """; + + JsonListBody? body = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.NotNull(body); + + foreach ((int i, string value) in body.Names.Index()) + { + outputHelper.WriteLine($"{i} is null? {value is null}"); + } + } + + private struct JsonStruct + { + public int Id { get; } + + public string Name { get; } + + public JsonStruct(int id, string name) + { + Id = id; + Name = name; + } + } + + [Fact] + public void DeserializeToStructTest() + { + const string input = """ + { + "Id": 1, + "Name": "ricardo" + } + """; + + JsonStruct r = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.Equal(0, r.Id); + Assert.Null(r.Name); + } + + private readonly struct JsonReadonlyStruct + { + public int Id { get; } + + public string Name { get; } + + public JsonReadonlyStruct(int id, string name) + { + Id = id; + Name = name; + } + } + + [Fact] + public void DeserializeToReadonlyStructTest() + { + const string input = """ + { + "Id": 1, + "Name": "ricardo" + } + """; + + JsonReadonlyStruct r = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.Equal(0, r.Id); + Assert.Null(r.Name); + } + + private record struct JsonRecordStruct(int Id, string Name); + + [Fact] + public void DeserializeToRecordStructTest() + { + const string input = """ + { + "Id": 1, + "Name": "ricardo" + } + """; + + JsonRecordStruct r = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.Equal(1, r.Id); + Assert.Equal("ricardo", r.Name); + } + + private readonly record struct JsonReadonlyRecordStruct(int Id, string Name); + + [Fact] + public void DeserializeToReadonlyRecordStructTest() + { + const string input = """ + { + "Id": 1, + "Name": "ricardo" + } + """; + + JsonReadonlyRecordStruct r = JsonSerializer.Deserialize(input, s_serializerOptions); + Assert.Equal(1, r.Id); + Assert.Equal("ricardo", r.Name); + } +} diff --git a/src/YaeBlog.Tests/JsonSerializationTests.cs b/src/YaeBlog.Tests/JsonSerializationTests.cs new file mode 100644 index 0000000..bd7bccc --- /dev/null +++ b/src/YaeBlog.Tests/JsonSerializationTests.cs @@ -0,0 +1,17 @@ +using System.Text.Json; +using Xunit.Abstractions; + +namespace YaeBlog.Tests; + +public sealed class JsonSerializationTests(ITestOutputHelper outputHelper) +{ + private record JsonBody(long Number); + + [Fact] + public void LongSerializeTest() + { + JsonBody body = new(long.MaxValue - 1); + string output = JsonSerializer.Serialize(body); + outputHelper.WriteLine(output); + } +} diff --git a/src/YaeBlog.Tests/OptionsMock.cs b/src/YaeBlog.Tests/OptionsMock.cs new file mode 100644 index 0000000..16238e7 --- /dev/null +++ b/src/YaeBlog.Tests/OptionsMock.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.Options; +using Moq; +using YaeBlog.Models; + +namespace YaeBlog.Tests; + +public static class OptionsMock +{ + public static Mock> CreateBlogOptionMock() + { + Mock> mock = new(); + + mock.SetupGet(o => o.Value) + .Returns(new BlogOptions { Root = "source", Announcement = string.Empty, Links = [], StartYear = 2021 }); + + return mock; + } +} diff --git a/src/YaeBlog.Tests/YaeBlog.Tests.csproj b/src/YaeBlog.Tests/YaeBlog.Tests.csproj new file mode 100644 index 0000000..5e2af34 --- /dev/null +++ b/src/YaeBlog.Tests/YaeBlog.Tests.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/YaeBlog/Abstraction/IEssayContentService.cs b/src/YaeBlog/Abstraction/IEssayContentService.cs similarity index 100% rename from YaeBlog/Abstraction/IEssayContentService.cs rename to src/YaeBlog/Abstraction/IEssayContentService.cs diff --git a/src/YaeBlog/Abstraction/IEssayScanService.cs b/src/YaeBlog/Abstraction/IEssayScanService.cs new file mode 100644 index 0000000..dee2a7d --- /dev/null +++ b/src/YaeBlog/Abstraction/IEssayScanService.cs @@ -0,0 +1,16 @@ +using YaeBlog.Models; + +namespace YaeBlog.Abstraction; + +public interface IEssayScanService +{ + public Task ScanContents(); + + /// + /// 将对应的博客文章保存在磁盘上 + /// + /// + /// 指定对应博客文章是否为草稿。因为BlogContent是不可变对象,因此提供该参数以方便publish的实现。 + /// + public Task SaveBlogContent(BlogContent content, bool isDraft = true); +} diff --git a/YaeBlog/Abstraction/IPostRenderProcessor.cs b/src/YaeBlog/Abstraction/IPostRenderProcessor.cs similarity index 100% rename from YaeBlog/Abstraction/IPostRenderProcessor.cs rename to src/YaeBlog/Abstraction/IPostRenderProcessor.cs diff --git a/YaeBlog/Abstraction/IPreRenderProcessor.cs b/src/YaeBlog/Abstraction/IPreRenderProcessor.cs similarity index 100% rename from YaeBlog/Abstraction/IPreRenderProcessor.cs rename to src/YaeBlog/Abstraction/IPreRenderProcessor.cs diff --git a/YaeBlog/Components/Anchor.razor b/src/YaeBlog/Components/Anchor.razor similarity index 100% rename from YaeBlog/Components/Anchor.razor rename to src/YaeBlog/Components/Anchor.razor diff --git a/src/YaeBlog/Components/App.razor b/src/YaeBlog/Components/App.razor new file mode 100644 index 0000000..ecdce81 --- /dev/null +++ b/src/YaeBlog/Components/App.razor @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/YaeBlog/Components/AppreciationCode.razor b/src/YaeBlog/Components/AppreciationCode.razor new file mode 100644 index 0000000..4d3e2b0 --- /dev/null +++ b/src/YaeBlog/Components/AppreciationCode.razor @@ -0,0 +1,29 @@ +
+
+ 微信赞赏码 +
+ 请我喝奶茶
+
+
+ +
+ 支付宝赞赏码 +
+ 请我吃晚饭
+
+
+ +
diff --git a/YaeBlog/Components/BlogInformationCard.razor b/src/YaeBlog/Components/BlogInformationCard.razor similarity index 89% rename from YaeBlog/Components/BlogInformationCard.razor rename to src/YaeBlog/Components/BlogInformationCard.razor index 11f8a88..2df351d 100644 --- a/YaeBlog/Components/BlogInformationCard.razor +++ b/src/YaeBlog/Components/BlogInformationCard.razor @@ -1,8 +1,9 @@ +@using Microsoft.Extensions.Options @using YaeBlog.Abstraction @using YaeBlog.Models @inject IEssayContentService Contents -@inject BlogOptions Options +@inject IOptions Options
@@ -43,7 +44,7 @@

- @(Options.Announcement) + @(Options.Value.Announcement)

diff --git a/YaeBlog/Components/EssayCard.razor b/src/YaeBlog/Components/EssayCard.razor similarity index 73% rename from YaeBlog/Components/EssayCard.razor rename to src/YaeBlog/Components/EssayCard.razor index 99cb3f1..1458f50 100644 --- a/YaeBlog/Components/EssayCard.razor +++ b/src/YaeBlog/Components/EssayCard.razor @@ -3,7 +3,7 @@
@@ -14,9 +14,7 @@ @foreach (string key in Essay.Tags) { }
diff --git a/src/YaeBlog/Components/Foonter.razor b/src/YaeBlog/Components/Foonter.razor new file mode 100644 index 0000000..100953a --- /dev/null +++ b/src/YaeBlog/Components/Foonter.razor @@ -0,0 +1,30 @@ +
+
+

+ 2021 - @(DateTimeOffset.Now.Year) © + + ,由 + + 驱动。 +

+

+ Build Commit # + +

+
+ +
+

+ +

+
+
+ +@code +{ + private static string DotnetVersion => $".NET {Environment.Version}"; + + private static string BuildCommitId => Environment.GetEnvironmentVariable("COMMIT_ID") ?? "local_build"; + + private static string BuildCommitUrl => $"https://git.rrricardo.top/jackfiled/YaeBlog/commit/{BuildCommitId}"; +} diff --git a/src/YaeBlog/Components/GitHeatMap.razor b/src/YaeBlog/Components/GitHeatMap.razor new file mode 100644 index 0000000..3051329 --- /dev/null +++ b/src/YaeBlog/Components/GitHeatMap.razor @@ -0,0 +1,122 @@ +@using YaeBlog.Models +@using YaeBlog.Services +@inject GitHeapMapService GitHeapMapInstance + +
+ + + @foreach ((int i, string text) in _monthIndices) + { + + } + + + @foreach ((int i, string text) in Weekdays.Index()) + { + + } + + + @foreach ((int i, GitContributionGroupedByWeek contribution) in _contributions.Index()) + { + + @foreach ((int j, GitContributionItem item) in contribution.Contributions.Index()) + { + + } + + } + + +
+ +@code { + private const int Width = 10; + private const int Spacing = 2; + private const int GridXOffset = 25; + private const int GridYOffset = 15; + + private readonly record struct MonthIndex(int Pos, string Month); + + private readonly List _monthIndices = []; + private readonly List _contributions = []; + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + _contributions.AddRange(await GitHeapMapInstance.GetGitContributionGroupedByWeek()); + + // 遍历寻找月份变化的时候,i指向上一个item + foreach ((int i, GitContributionGroupedByWeek contributionGroupedByWeek) in _contributions.Skip(1).Index()) + { + if (contributionGroupedByWeek.Monday.Month == _contributions[i].Monday.Month) + { + continue; + } + + // 找到当前周和上一周月份不一致 + _monthIndices.Add(new MonthIndex(i + 1, Months[contributionGroupedByWeek.Monday.Month - 1])); + } + } + + private SvgViewBox CalculateViewBox() + { + int width = GridXOffset + Width * _contributions.Count + Spacing * (_contributions.Count - 1); + const int height = GridYOffset + Width * 7 + Spacing * 6; + + // 添加一个10px的额外大小防止出现意外问题 + return new SvgViewBox(0, 0, width + 10, height + 10); + } + + private static SvgTransform GlobalMonthTransform => SvgTransform.CreateBuilder().Translate(23, 10).Build(); + + private static SvgTransform GlobalWeekTransform => SvgTransform.CreateBuilder().Translate(2, 24).Build(); + + private static SvgTransform GlobalMapTransform => SvgTransform.CreateBuilder().Translate(GridXOffset, GridYOffset).Build(); + + private static readonly List Months = + [ + "1月", "2月", "3月", "4月", "5月", "6月", + "7月", "8月", "9月", "10月", "11月", "12月" + ]; + + // private static readonly List Weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]; + private static readonly List Weekdays = ["周一", "周三", "周五"]; + + private static SvgTransform WeekdayGridTransform(int y) + { + return SvgTransform.CreateBuilder().Translate(0, y * (Width + Spacing)).Build(); + } + + private static SvgTransform WeekGridTransform(int x) + { + return SvgTransform.CreateBuilder().Translate(x * (Width + Spacing)).Build(); + } + + private static SvgTransform MonthTextTransform(int x) + { + return SvgTransform.CreateBuilder().Translate(x * (Width + Spacing)).Build(); + } + + private static SvgTransform DayTextTransform(int y) + { + // We only show Monday, Wednesday and Friday, so there are two days between texts. + return SvgTransform.CreateBuilder().Translate(0, y * 2 * (Width + Spacing)).Build(); + } + + private static string GetColorByContribution(long contribution) + { + return contribution switch + { + 0 => "fill-gray-200", + 1 or 2 => "fill-blue-100", + 3 or 4 => "fill-blue-300", + 5 or 6 => "fill-blue-500", + 7 or 8 => "fill-blue-700", + _ => "fill-blue-800" + }; + } + +} diff --git a/src/YaeBlog/Components/Layout/BlogLayout.razor b/src/YaeBlog/Components/Layout/BlogLayout.razor new file mode 100644 index 0000000..1eb1c78 --- /dev/null +++ b/src/YaeBlog/Components/Layout/BlogLayout.razor @@ -0,0 +1,28 @@ +@inherits LayoutComponentBase + +@attribute [StreamRendering] + +
+
+ + +
+
+ + + + +
+
+
+ +
+ @Body +
+ + +
diff --git a/YaeBlog/Layout/MainLayout.razor b/src/YaeBlog/Components/Layout/MainLayout.razor similarity index 86% rename from YaeBlog/Layout/MainLayout.razor rename to src/YaeBlog/Components/Layout/MainLayout.razor index 65227a3..3260e73 100644 --- a/YaeBlog/Layout/MainLayout.razor +++ b/src/YaeBlog/Components/Layout/MainLayout.razor @@ -17,13 +17,11 @@ + Text="关于"/> + Text="友链"/>
diff --git a/YaeBlog/Components/LicenseDisclaimer.razor b/src/YaeBlog/Components/LicenseDisclaimer.razor similarity index 72% rename from YaeBlog/Components/LicenseDisclaimer.razor rename to src/YaeBlog/Components/LicenseDisclaimer.razor index ab77a81..2fb886e 100644 --- a/YaeBlog/Components/LicenseDisclaimer.razor +++ b/src/YaeBlog/Components/LicenseDisclaimer.razor @@ -1,10 +1,7 @@ -@using YaeBlog.Models -@inject BlogOptions Options -
- 文章作者:Ricardo Ren + 文章作者:初冬的朝阳
@@ -21,9 +18,20 @@ 许可协议,诸位读者如有兴趣可任意转载,不必征询许可,但请注明“转载自 - Ricardo's Blog + Jackfiled's Blog ”。
+ +
+
+

如果觉得不错的话,可以支持一下作者哦~

+
+ +
+ +
+
+
diff --git a/YaeBlog/Components/PageAnchor.razor b/src/YaeBlog/Components/PageAnchor.razor similarity index 100% rename from YaeBlog/Components/PageAnchor.razor rename to src/YaeBlog/Components/PageAnchor.razor diff --git a/src/YaeBlog/Components/Pages/About.razor b/src/YaeBlog/Components/Pages/About.razor new file mode 100644 index 0000000..3e4821e --- /dev/null +++ b/src/YaeBlog/Components/Pages/About.razor @@ -0,0 +1,99 @@ +@page "/about" + + + 关于 + + +
+
+

关于

+
+ +
+ 把字刻在石头上!(・’ω’・) +
+ +
+
+
+

关于我

+
+ +
+
+

+ 正在明光村幼儿园附属研究生院攻读计算机科学与技术的硕士学位,研究AI编译器和异构编译器。 +

+ +

+ 一般在互联网上使用初冬的朝阳或者 + jackfiled的名字活动。 + (都是ICP备案过的人了,网名似乎没有太大的用处) +

+

+ Fun Fact:jackfiled这个名字来自于2020年我使用链接在树莓派上的9英寸屏幕注册 + GitHub的一时兴起,并没有任何特定的含义。 + 初冬的朝阳则是源自初中,具体典故已不可考。 + 至少到目前为止,还没有在要求唯一ID的平台遇见重名的情况。 + 我的真实名字似乎也是如此。 +

+
+ +
+

+ 主要是一个.NET程序员,目前也在尝试写一点Rust。 + + 总体上对于编程语言的态度是“大家都是我的翅膀.jpg”。 + +

+

+ 写过一些前后端分离的项目,对于RISC-V相关的开发项目也颇感兴趣。 +

+

+ 常常因为现实的压力而写一些C/C++,现在就在和MLIR殊死搏斗。 +

+

+ 日常使用Arch Linux,KISS的原则深得我心。 +

+
+ +
+

+ 100%社恐。日常生活是宅在电脑前面自言自语。 +

+

+ 兴趣活动是读书和看番,目前在玩戴森球计划和三角洲。2022年~2024年的时候沉迷于原神,现在偶尔还会登上去过一过剧情。 +

+
+
+
+ +
+
+

关于本站

+
+ +
+
+

+ 本站肇始于2021年下半年,在开始的两年中个人网站和博客是分别的两个网站,个人网站是裸HTML写的,博客是用 + + 的。 +

+
+ +
+

+ 2024年,我们决定使用.NET技术完全重构两个网站,合二为一。虽然目前这个版本还是一个半成品,但是我们一定会努力的~(确信。 +

+
+ +
+

+ 2025年,我们将使用的样式库从Bootstrap迁移到Tailwind CSS,将现代的前端技术同Blazor结合起来。 +

+
+
+
+
+
diff --git a/YaeBlog/Pages/Archives.razor b/src/YaeBlog/Components/Pages/Archives.razor similarity index 82% rename from YaeBlog/Pages/Archives.razor rename to src/YaeBlog/Components/Pages/Archives.razor index 883f33b..55204f0 100644 --- a/YaeBlog/Pages/Archives.razor +++ b/src/YaeBlog/Components/Pages/Archives.razor @@ -19,7 +19,7 @@ - @foreach (IGrouping group in _essays) + @foreach (IGrouping group in _essays) {
@@ -30,7 +30,7 @@
@foreach (BlogEssay essay in group) { - +
@(essay.PublishTime.ToString("MM月dd日")) @@ -51,13 +51,13 @@
@code { - private readonly List> _essays = []; + private readonly List> _essays = []; protected override void OnInitialized() { base.OnInitialized(); _essays.AddRange(from essay in Contents.Essays - group essay by new DateTime(essay.PublishTime.Year, 1, 1)); + group essay by new DateTimeOffset(essay.PublishTime.Year, 1, 1,0, 0, 0, TimeSpan.Zero)); } } diff --git a/YaeBlog/Pages/BlogIndex.razor b/src/YaeBlog/Components/Pages/BlogIndex.razor similarity index 87% rename from YaeBlog/Pages/BlogIndex.razor rename to src/YaeBlog/Components/Pages/BlogIndex.razor index 7c8b683..f4ff07e 100644 --- a/YaeBlog/Pages/BlogIndex.razor +++ b/src/YaeBlog/Components/Pages/BlogIndex.razor @@ -6,7 +6,7 @@ @inject NavigationManager NavigationInstance - Ricardo's Blog + Jackfiled's Blog
@@ -39,6 +39,11 @@ { _page = Page ?? 1; _pageCount = Contents.Count / EssaysPerPage + 1; + (_pageCount, int reminder) = int.DivRem(Contents.Count, EssaysPerPage); + if (reminder > 0) + { + _pageCount += 1; + } if (EssaysPerPage * _page > Contents.Count + EssaysPerPage) { diff --git a/YaeBlog/Pages/Essays.razor b/src/YaeBlog/Components/Pages/Essays.razor similarity index 73% rename from YaeBlog/Pages/Essays.razor rename to src/YaeBlog/Components/Pages/Essays.razor index d1d1b74..9aa0a94 100644 --- a/YaeBlog/Pages/Essays.razor +++ b/src/YaeBlog/Components/Pages/Essays.razor @@ -12,47 +12,42 @@
-

@(_essay!.Title)

-
-
-
- -
-
-
- @(_essay!.PublishTime.ToString("yyyy-MM-dd")) + -
-
- 总字数:@(_essay!.WordCount)字,预计阅读时间 @(_essay!.ReadTime)。 +
+ 总字数:@(_essay!.WordCount)字,预计阅读时间 @(_essay!.ReadTime) +
-
-
- @((MarkupString)_essay!.HtmlContent) -
- -
- -
-
-
-
+

文章目录

@@ -93,8 +88,17 @@ }
-
+
+
+ @((MarkupString)_essay!.HtmlContent) +
+ +
+ +
+
+
@code { diff --git a/YaeBlog/Pages/Friends.razor b/src/YaeBlog/Components/Pages/Friends.razor similarity index 85% rename from YaeBlog/Pages/Friends.razor rename to src/YaeBlog/Components/Pages/Friends.razor index b46369f..648a198 100644 --- a/YaeBlog/Pages/Friends.razor +++ b/src/YaeBlog/Components/Pages/Friends.razor @@ -1,6 +1,7 @@ @page "/friends" +@using Microsoft.Extensions.Options @using YaeBlog.Models -@inject BlogOptions Options +@inject IOptions BlogOptionInstance 友链 @@ -18,7 +19,7 @@
- @foreach (FriendLink link in Options.Links) + @foreach (FriendLink link in BlogOptionInstance.Value.Links.Where(i => i is not null).Select(i => i!)) {
- -@code { - -} diff --git a/src/YaeBlog/Components/Pages/Index.razor b/src/YaeBlog/Components/Pages/Index.razor new file mode 100644 index 0000000..33393a7 --- /dev/null +++ b/src/YaeBlog/Components/Pages/Index.razor @@ -0,0 +1,101 @@ +@page "/" +@using YaeBlog.Abstraction +@using YaeBlog.Models +@inject IEssayContentService EssayContentInstance + + + Jackfiled's Index + + +
+ + +
+

恕我不能亲自为您沏茶,还是非常欢迎您来,能在广阔的互联网世界中发现这里实属不易。

+
+ +
+

+ 正在攻读计算机科学与技术的硕士学位,研究方向是AI编译和异构编译! + 喜欢优雅的代码,香甜的蛋糕等等一切可爱的事物。 + 更多的情报请见。 +

+

+

+

+ 中收集了我的各种奇思妙想,如果感兴趣欢迎移步。 + @if (_latestEssay is not null) + { + + 最新的一期博客关注 。 + + } +

+

+ 日常的代码开发使用自建的进行,个人 + 开发的各种项目都可以在上面找到。下面的热力图展示了我在Git上的各种动态(Everything as Code)。 +

+
+ +
+ +
+
+ +@code { + private BlogEssay? _latestEssay; + + protected override void OnInitialized() + { + base.OnInitialized(); + _latestEssay = EssayContentInstance.Essays.OrderByDescending(e => e.UpdateTime).FirstOrDefault(); + } + +} diff --git a/src/YaeBlog/Components/Pages/Index.razor.css b/src/YaeBlog/Components/Pages/Index.razor.css new file mode 100644 index 0000000..53f2acd --- /dev/null +++ b/src/YaeBlog/Components/Pages/Index.razor.css @@ -0,0 +1,47 @@ +.fa-brands { + font-family: "Font Awesome 7 Brands", "Font Awesome 7 Free"; + font-style: normal; + font-synthesis: none; + font-variant: normal; + line-height: 1; + text-rendering: auto; + font-weight: 400; +} + +.fa-github::before { + content: "\f09b"; + color: #24292e; +} + +.fa-bilibili::before { + content: "\e3d9"; + color: #00AEEC; +} + +.gitea-icon { + display: inline-block; + vertical-align: -0.125em; + width: 1em; + height: 1em; + + background-image: url("https://docs.gitea.com/img/gitea.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + + user-select: none; +} + +.rednote-icon { + display: inline-block; + vertical-align: -0.125em; + width: 1em; + height: 1em; + + background-image: url("images/xiaohongshu-seeklogo.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + + user-select: none; +} diff --git a/YaeBlog/Pages/NotFound.razor b/src/YaeBlog/Components/Pages/NotFound.razor similarity index 100% rename from YaeBlog/Pages/NotFound.razor rename to src/YaeBlog/Components/Pages/NotFound.razor diff --git a/YaeBlog/Pages/Tags.razor b/src/YaeBlog/Components/Pages/Tags.razor similarity index 100% rename from YaeBlog/Pages/Tags.razor rename to src/YaeBlog/Components/Pages/Tags.razor diff --git a/YaeBlog/Components/Pagination.razor b/src/YaeBlog/Components/Pagination.razor similarity index 100% rename from YaeBlog/Components/Pagination.razor rename to src/YaeBlog/Components/Pagination.razor diff --git a/YaeBlog/Components/Routes.razor b/src/YaeBlog/Components/Routes.razor similarity index 100% rename from YaeBlog/Components/Routes.razor rename to src/YaeBlog/Components/Routes.razor diff --git a/YaeBlog/_Imports.razor b/src/YaeBlog/Components/_Imports.razor similarity index 85% rename from YaeBlog/_Imports.razor rename to src/YaeBlog/Components/_Imports.razor index e5a1ddf..0526866 100644 --- a/YaeBlog/_Imports.razor +++ b/src/YaeBlog/Components/_Imports.razor @@ -6,5 +6,6 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop -@using YaeBlog @using YaeBlog.Components +@using BlazorSvgComponents +@using BlazorSvgComponents.Models diff --git a/YaeBlog/Controllers/FilesController.cs b/src/YaeBlog/Controllers/FilesController.cs similarity index 100% rename from YaeBlog/Controllers/FilesController.cs rename to src/YaeBlog/Controllers/FilesController.cs diff --git a/src/YaeBlog/Directory.Build.targets b/src/YaeBlog/Directory.Build.targets new file mode 100644 index 0000000..fd87244 --- /dev/null +++ b/src/YaeBlog/Directory.Build.targets @@ -0,0 +1,53 @@ + + + pnpm install + pnpm run build + --output + + + + <_RestoreClientAssetsBeforeTargets Condition="'$(TargetFramework)' == ''">DispatchToInnerBuilds + + + + + + + + + + <_ClientAssetsOutputFullPath>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)ClientAssets')) + + + + + + + <_ClientAssetsBuildOutput Include="$(IntermediateOutputPath)ClientAssets\**"/> + + + + + + + + + + + + + + + + + + diff --git a/src/YaeBlog/Dockerfile b/src/YaeBlog/Dockerfile new file mode 100644 index 0000000..515b8c7 --- /dev/null +++ b/src/YaeBlog/Dockerfile @@ -0,0 +1,13 @@ +FROM mcr.azure.cn/dotnet/aspnet:10.0 + +ARG COMMIT_ID +ENV COMMIT_ID=${COMMIT_ID} + +WORKDIR /app +COPY out/ ./ +COPY source/ ./source/ +COPY src/YaeBlog/appsettings.json . + +ENV BLOG__ROOT="./source" + +ENTRYPOINT ["dotnet", "YaeBlog.dll", "serve"] diff --git a/src/YaeBlog/Exceptions/BlogCommandException.cs b/src/YaeBlog/Exceptions/BlogCommandException.cs new file mode 100644 index 0000000..0db929d --- /dev/null +++ b/src/YaeBlog/Exceptions/BlogCommandException.cs @@ -0,0 +1,12 @@ +namespace YaeBlog.Core.Exceptions; + +public class BlogCommandException : Exception +{ + public BlogCommandException(string message) : base(message) + { + } + + public BlogCommandException(string message, Exception innerException) : base(message, innerException) + { + } +} diff --git a/YaeBlog/Exceptions/BlogFileException.cs b/src/YaeBlog/Exceptions/BlogFileException.cs similarity index 100% rename from YaeBlog/Exceptions/BlogFileException.cs rename to src/YaeBlog/Exceptions/BlogFileException.cs diff --git a/src/YaeBlog/Exceptions/GiteaFetchException.cs b/src/YaeBlog/Exceptions/GiteaFetchException.cs new file mode 100644 index 0000000..f795dbf --- /dev/null +++ b/src/YaeBlog/Exceptions/GiteaFetchException.cs @@ -0,0 +1,16 @@ +namespace YaeBlog.Core.Exceptions; + +public class GiteaFetchException : Exception +{ + public GiteaFetchException() : base() + { + } + + public GiteaFetchException(string message) : base(message) + { + } + + public GiteaFetchException(string message, Exception innerException) : base(message, innerException) + { + } +} diff --git a/src/YaeBlog/Extensions/AngleSharpExtensions.cs b/src/YaeBlog/Extensions/AngleSharpExtensions.cs new file mode 100644 index 0000000..a299ab8 --- /dev/null +++ b/src/YaeBlog/Extensions/AngleSharpExtensions.cs @@ -0,0 +1,18 @@ +using AngleSharp.Dom; + +namespace YaeBlog.Extensions; + +public static class AngleSharpExtensions +{ + public static IEnumerable EnumerateParentElements(this IElement element) + { + IElement? e = element.ParentElement; + + while (e is not null) + { + IElement c = e; + e = e.ParentElement; + yield return c; + } + } +} diff --git a/src/YaeBlog/Extensions/DateOnlyExtensions.cs b/src/YaeBlog/Extensions/DateOnlyExtensions.cs new file mode 100644 index 0000000..3568d72 --- /dev/null +++ b/src/YaeBlog/Extensions/DateOnlyExtensions.cs @@ -0,0 +1,22 @@ +namespace YaeBlog.Extensions; + +public static class DateOnlyExtensions +{ + extension(DateOnly date) + { + public static DateOnly Today => DateOnly.FromDateTime(DateTime.Now); + + public DateOnly LastMonday + { + get + { + return date.DayOfWeek switch + { + DayOfWeek.Monday => date, + DayOfWeek.Sunday => date.AddDays(-6), + _ => date.AddDays(1 - (int)date.DayOfWeek) + }; + } + } + } +} diff --git a/src/YaeBlog/Extensions/ServiceCollectionExtensions.cs b/src/YaeBlog/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..3fefec4 --- /dev/null +++ b/src/YaeBlog/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,36 @@ +using Markdig; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace YaeBlog.Extensions; + +public static class ServiceCollectionExtensions +{ + extension(IServiceCollection collection) + { + public IServiceCollection AddMarkdig() + { + MarkdownPipelineBuilder builder = new(); + + builder.UseAdvancedExtensions(); + + collection.AddSingleton(_ => builder.Build()); + + return collection; + } + + public IServiceCollection AddYamlParser() + { + DeserializerBuilder deserializerBuilder = new(); + deserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance); + deserializerBuilder.IgnoreUnmatchedProperties(); + collection.AddSingleton(deserializerBuilder.Build()); + + SerializerBuilder serializerBuilder = new(); + serializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance); + collection.AddSingleton(serializerBuilder.Build()); + + return collection; + } + } +} diff --git a/src/YaeBlog/Extensions/WebApplicationBuilderExtensions.cs b/src/YaeBlog/Extensions/WebApplicationBuilderExtensions.cs new file mode 100644 index 0000000..192412c --- /dev/null +++ b/src/YaeBlog/Extensions/WebApplicationBuilderExtensions.cs @@ -0,0 +1,63 @@ +using AngleSharp; +using Microsoft.Extensions.Options; +using YaeBlog.Abstraction; +using YaeBlog.Services; +using YaeBlog.Models; +using YaeBlog.Processors; + +namespace YaeBlog.Extensions; + +public static class WebApplicationBuilderExtensions +{ + extension(WebApplicationBuilder builder) + { + public WebApplicationBuilder AddYaeBlog() + { + builder.ConfigureOptions(BlogOptions.OptionName) + .ConfigureOptions(GiteaOptions.OptionName); + + builder.Services.AddHttpClient() + .AddMarkdig() + .AddYamlParser(); + + builder.Services.AddSingleton(_ => Configuration.Default) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddSingleton(); + + return builder; + } + + public WebApplicationBuilder AddYaeCommand(string[] arguments) + { + builder.Services.AddHostedService(provider => + { + IEssayScanService essayScanService = provider.GetRequiredService(); + IOptions blogOptions = provider.GetRequiredService>(); + ILogger logger = provider.GetRequiredService>(); + IHostApplicationLifetime applicationLifetime = provider.GetRequiredService(); + + return new YaeCommandService(arguments, essayScanService, provider, blogOptions, logger, + applicationLifetime); + }); + + return builder; + } + + private WebApplicationBuilder ConfigureOptions(string optionSectionName) where T : class + { + builder.Services + .AddOptions() + .Bind(builder.Configuration.GetSection(optionSectionName)) + .ValidateDataAnnotations(); + return builder; + } + } +} diff --git a/YaeBlog/Extensions/WebApplicationExtensions.cs b/src/YaeBlog/Extensions/WebApplicationExtensions.cs similarity index 100% rename from YaeBlog/Extensions/WebApplicationExtensions.cs rename to src/YaeBlog/Extensions/WebApplicationExtensions.cs diff --git a/src/YaeBlog/Models/BlogContent.cs b/src/YaeBlog/Models/BlogContent.cs new file mode 100644 index 0000000..1765249 --- /dev/null +++ b/src/YaeBlog/Models/BlogContent.cs @@ -0,0 +1,20 @@ +namespace YaeBlog.Models; + +/// +/// 单个博客文件的所有数据和元数据 +/// +/// 博客文件 +/// 文件中的MD元数据 +/// 文件内容 +/// 是否为草稿 +/// 博客中使用的文件 +public record BlogContent( + FileInfo BlogFile, + MarkdownMetadata Metadata, + string Content, + bool IsDraft, + List Images, + List NotfoundImages) +{ + public string BlogName => BlogFile.Name.Split('.')[0]; +} diff --git a/src/YaeBlog/Models/BlogContents.cs b/src/YaeBlog/Models/BlogContents.cs new file mode 100644 index 0000000..7540078 --- /dev/null +++ b/src/YaeBlog/Models/BlogContents.cs @@ -0,0 +1,12 @@ +using System.Collections; +using System.Collections.Concurrent; + +namespace YaeBlog.Models; + +public record BlogContents(ConcurrentBag Drafts, ConcurrentBag Posts) + : IEnumerable +{ + public IEnumerator GetEnumerator() => Posts.Concat(Drafts).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/src/YaeBlog/Models/BlogEssay.cs b/src/YaeBlog/Models/BlogEssay.cs new file mode 100644 index 0000000..44e7283 --- /dev/null +++ b/src/YaeBlog/Models/BlogEssay.cs @@ -0,0 +1,34 @@ +namespace YaeBlog.Models; + +public record BlogEssay( + string Title, + string FileName, + bool IsDraft, + DateTimeOffset PublishTime, + DateTimeOffset UpdateTime, + string Description, + uint WordCount, + string ReadTime, + List Tags, + string HtmlContent) : IComparable +{ + public string EssayLink => $"/blog/essays/{FileName}"; + + public override string ToString() => $"{Title}-{PublishTime}"; + + public int CompareTo(BlogEssay? other) + { + if (other is null) + { + return -1; + } + + // 草稿文章应当排在前面 + if (IsDraft != other.IsDraft) + { + return IsDraft ? -1 : 1; + } + + return other.PublishTime.CompareTo(PublishTime); + } +} diff --git a/YaeBlog/Models/BlogHeadline.cs b/src/YaeBlog/Models/BlogHeadline.cs similarity index 100% rename from YaeBlog/Models/BlogHeadline.cs rename to src/YaeBlog/Models/BlogHeadline.cs diff --git a/src/YaeBlog/Models/BlogImageInfo.cs b/src/YaeBlog/Models/BlogImageInfo.cs new file mode 100644 index 0000000..ee63641 --- /dev/null +++ b/src/YaeBlog/Models/BlogImageInfo.cs @@ -0,0 +1,44 @@ +using System.Text; + +namespace YaeBlog.Models; + +public record BlogImageInfo(FileInfo File, long Width, long Height, string MineType, byte[] Content, bool IsUsed) + : IComparable +{ + public int Size => Content.Length; + + public override string ToString() + { + StringBuilder builder = new(); + + builder.AppendLine($"Blog image {File.Name}:"); + builder.AppendLine($"\tWidth: {Width}; Height: {Height}"); + builder.AppendLine($"\tSize: {FormatSize()}"); + builder.AppendLine($"\tImage Format: {MineType}"); + + return builder.ToString(); + } + + public int CompareTo(BlogImageInfo? other) + { + if (other is null) + { + return -1; + } + + return other.Size.CompareTo(Size); + } + + private string FormatSize() + { + double size = Size; + if (size / 1024 > 3) + { + size /= 1024; + + return size / 1024 > 3 ? $"{size / 1024}MB" : $"{size}KB"; + } + + return $"{size}B"; + } +} diff --git a/src/YaeBlog/Models/BlogOptions.cs b/src/YaeBlog/Models/BlogOptions.cs new file mode 100644 index 0000000..e6fd993 --- /dev/null +++ b/src/YaeBlog/Models/BlogOptions.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; + +namespace YaeBlog.Models; + +/// +/// 友链模型类 +/// +public class FriendLink +{ + [Required] public required string Name { get; init; } + + [Required] public required string Description { get; init; } + + [Required] public required string Link { get; init; } + + [Required] public required string AvatarImage { get; init; } +} + +public class BlogOptions +{ + public const string OptionName = "Blog"; + + [Required] public required string Root { get; init; } + + [Required] public required string Announcement { get; init; } + + [Required] public required int StartYear { get; init; } + + [Required] public required List Links { get; init; } +} diff --git a/YaeBlog/Models/EssayTag.cs b/src/YaeBlog/Models/EssayTag.cs similarity index 100% rename from YaeBlog/Models/EssayTag.cs rename to src/YaeBlog/Models/EssayTag.cs diff --git a/src/YaeBlog/Models/GiteaOptions.cs b/src/YaeBlog/Models/GiteaOptions.cs new file mode 100644 index 0000000..e333452 --- /dev/null +++ b/src/YaeBlog/Models/GiteaOptions.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace YaeBlog.Models; + +public class GiteaOptions +{ + public const string OptionName = "Gitea"; + + [Required] public required string BaseAddress { get; init; } + + [Required] public required string ApiKey { get; init; } + + [Required] public required string HeatMapUsername { get; init; } +} diff --git a/src/YaeBlog/Models/HeatMapItem.cs b/src/YaeBlog/Models/HeatMapItem.cs new file mode 100644 index 0000000..c61a30e --- /dev/null +++ b/src/YaeBlog/Models/HeatMapItem.cs @@ -0,0 +1,5 @@ +namespace YaeBlog.Models; + +public record GitContributionItem(DateOnly Time, long ContributionCount); + +public record GitContributionGroupedByWeek(DateOnly Monday, List Contributions); diff --git a/YaeBlog/Models/MarkdownMetadata.cs b/src/YaeBlog/Models/MarkdownMetadata.cs similarity index 64% rename from YaeBlog/Models/MarkdownMetadata.cs rename to src/YaeBlog/Models/MarkdownMetadata.cs index 8359e7d..9aab220 100644 --- a/YaeBlog/Models/MarkdownMetadata.cs +++ b/src/YaeBlog/Models/MarkdownMetadata.cs @@ -4,7 +4,9 @@ public class MarkdownMetadata { public string? Title { get; set; } - public DateTime? Date { get; set; } + public string? Date { get; set; } + + public string? UpdateTime { get; set; } public List? Tags { get; set; } } diff --git a/YaeBlog/Processors/EssayStylesPostRenderProcessor.cs b/src/YaeBlog/Processors/EssayStylesPostRenderProcessor.cs similarity index 58% rename from YaeBlog/Processors/EssayStylesPostRenderProcessor.cs rename to src/YaeBlog/Processors/EssayStylesPostRenderProcessor.cs index 8329d18..ca4d27c 100644 --- a/YaeBlog/Processors/EssayStylesPostRenderProcessor.cs +++ b/src/YaeBlog/Processors/EssayStylesPostRenderProcessor.cs @@ -1,6 +1,7 @@ using AngleSharp; using AngleSharp.Dom; using YaeBlog.Abstraction; +using YaeBlog.Extensions; using YaeBlog.Models; namespace YaeBlog.Processors; @@ -15,25 +16,26 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor public async Task ProcessAsync(BlogEssay essay) { BrowsingContext context = new(Configuration.Default); - IDocument document = await context.OpenAsync( - req => req.Content(essay.HtmlContent)); + IDocument document = await context.OpenAsync(req => req.Content(essay.HtmlContent)); ApplyGlobalCssStyles(document); BeatifyTable(document); + BeatifyList(document); + BeatifyInlineCode(document); - return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml); + return essay with { HtmlContent = document.DocumentElement.OuterHtml }; } private readonly Dictionary _globalCssStyles = new() { - { "pre", "p-4 bg-slate-300 rounded-sm overflow-x-auto" }, + { "pre", "p-4 bg-gray-100 rounded-sm overflow-x-auto" }, { "h2", "text-3xl font-bold py-4" }, { "h3", "text-2xl font-bold py-3" }, { "h4", "text-xl font-bold py-2" }, { "h5", "text-lg font-bold py-1" }, { "p", "p-2" }, { "img", "w-11/12 block mx-auto my-2 rounded-md shadow-md" }, - { "ul", "list-disc pl-2" } + { "a", "text-blue-600" } }; private void ApplyGlobalCssStyles(IDocument document) @@ -99,4 +101,61 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor } } } + + /// + /// 美化各种列表元素 + /// + /// + private static void BeatifyList(IDocument document) + { + foreach (IElement listElement in from e in document.All + where e.LocalName is "ol" or "ul" + select e) + { + // 给有序或者无序列表添加不同的样式 + listElement.ClassList.Add("ml-10"); + switch (listElement.LocalName) + { + case "ul": + { + listElement.ClassList.Add("list-disc"); + break; + } + case "ol": + { + listElement.ClassList.Add("list-decimal"); + break; + } + } + + foreach (IElement liElement in from e in listElement.Children + where e.LocalName == "li" + select e) + { + // 修改
  • 元素中的

    元素样式 + // 默认的p-2间距有点太宽了 + foreach (IElement pElement in from e in liElement.Children + where e.LocalName == "p" + select e) + { + pElement.ClassList.Remove("p-2"); + pElement.ClassList.Add("p-1"); + } + } + } + } + + private static void BeatifyInlineCode(IDocument document) + { + // 选择不在

    元素内的元素
    +        // 即行内代码
    +        IEnumerable inlineCodes = from e in document.All
    +            where e.LocalName == "code" && e.EnumerateParentElements().All(p => p.LocalName != "pre")
    +            select e;
    +
    +        foreach (IElement e in inlineCodes)
    +        {
    +            e.ClassList.Add("bg-gray-100 inline p-1 rounded-xs");
    +        }
    +    }
     }
    diff --git a/YaeBlog/Processors/HeadlinePostRenderProcessor.cs b/src/YaeBlog/Processors/HeadlinePostRenderProcessor.cs
    similarity index 97%
    rename from YaeBlog/Processors/HeadlinePostRenderProcessor.cs
    rename to src/YaeBlog/Processors/HeadlinePostRenderProcessor.cs
    index 955098e..bd2b76c 100644
    --- a/YaeBlog/Processors/HeadlinePostRenderProcessor.cs
    +++ b/src/YaeBlog/Processors/HeadlinePostRenderProcessor.cs
    @@ -67,7 +67,7 @@ public class HeadlinePostRenderProcessor(
                 logger.LogWarning("Failed to add headline of {}.", essay.FileName);
             }
     
    -        return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml);
    +        return essay with { HtmlContent = document.DocumentElement.OuterHtml };
         }
     
         private static BlogHeadline ParserHeadlineElement(IElement element)
    diff --git a/YaeBlog/Processors/ImagePostRenderProcessor.cs b/src/YaeBlog/Processors/ImagePostRenderProcessor.cs
    similarity index 69%
    rename from YaeBlog/Processors/ImagePostRenderProcessor.cs
    rename to src/YaeBlog/Processors/ImagePostRenderProcessor.cs
    index f64a03a..75f3389 100644
    --- a/YaeBlog/Processors/ImagePostRenderProcessor.cs
    +++ b/src/YaeBlog/Processors/ImagePostRenderProcessor.cs
    @@ -7,7 +7,14 @@ using YaeBlog.Models;
     
     namespace YaeBlog.Processors;
     
    -public class ImagePostRenderProcessor(ILogger logger,
    +/// 
    +/// 图片地址路径后处理器
    +/// 将本地图片地址修改为图片API地址
    +/// 
    +/// 
    +/// 
    +public class ImagePostRenderProcessor(
    +    ILogger logger,
         IOptions options)
         : IPostRenderProcessor
     {
    @@ -29,22 +36,27 @@ public class ImagePostRenderProcessor(ILogger logger,
                 if (attr is not null)
                 {
                     logger.LogDebug("Found image link: '{}'", attr.Value);
    -                attr.Value = GenerateImageLink(attr.Value, essay.FileName);
    +                attr.Value = GenerateImageLink(attr.Value, essay.FileName, essay.IsDraft);
                 }
             }
    -        return essay.WithNewHtmlContent(html.DocumentElement.OuterHtml);
    +
    +        return essay with { HtmlContent = html.DocumentElement.OuterHtml };
         }
     
         public string Name => nameof(ImagePostRenderProcessor);
     
    -    private string GenerateImageLink(string filename, string essayFilename)
    +    private string GenerateImageLink(string filename, string essayFilename, bool isDraft)
         {
    +        // 如果图片路径中没有包含文件名
    +        // 则添加文件名
             if (!filename.Contains(essayFilename))
             {
                 filename = Path.Combine(essayFilename, filename);
             }
     
    -        filename = Path.Combine(_options.Root, "posts", filename);
    +        filename = isDraft
    +            ? Path.Combine(_options.Root, "drafts", filename)
    +            : Path.Combine(_options.Root, "posts", filename);
     
             if (!Path.Exists(filename))
             {
    @@ -53,7 +65,7 @@ public class ImagePostRenderProcessor(ILogger logger,
             }
     
             string imageLink = "api/files/" + filename;
    -        logger.LogDebug("Generate image link '{}' for image file '{}'.",
    +        logger.LogDebug("Generate image link '{link}' for image file '{filename}'.",
                 imageLink, filename);
     
             return imageLink;
    diff --git a/src/YaeBlog/Program.cs b/src/YaeBlog/Program.cs
    new file mode 100644
    index 0000000..d100ee0
    --- /dev/null
    +++ b/src/YaeBlog/Program.cs
    @@ -0,0 +1,22 @@
    +using YaeBlog.Components;
    +using YaeBlog.Extensions;
    +
    +WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
    +
    +builder.Services.AddRazorComponents()
    +    .AddInteractiveServerComponents();
    +builder.Services.AddControllers();
    +builder.AddYaeBlog();
    +builder.AddYaeCommand(args);
    +
    +WebApplication application = builder.Build();
    +
    +application.MapStaticAssets();
    +application.UseAntiforgery();
    +application.UseYaeBlog();
    +
    +application.MapRazorComponents()
    +    .AddInteractiveServerRenderMode();
    +application.MapControllers();
    +
    +await application.RunAsync();
    diff --git a/YaeBlog/Properties/launchSettings.json b/src/YaeBlog/Properties/launchSettings.json
    similarity index 100%
    rename from YaeBlog/Properties/launchSettings.json
    rename to src/YaeBlog/Properties/launchSettings.json
    diff --git a/YaeBlog/Services/BlogChangeWatcher.cs b/src/YaeBlog/Services/BlogChangeWatcher.cs
    similarity index 100%
    rename from YaeBlog/Services/BlogChangeWatcher.cs
    rename to src/YaeBlog/Services/BlogChangeWatcher.cs
    diff --git a/YaeBlog/Services/BlogHostedService.cs b/src/YaeBlog/Services/BlogHostedService.cs
    similarity index 100%
    rename from YaeBlog/Services/BlogHostedService.cs
    rename to src/YaeBlog/Services/BlogHostedService.cs
    diff --git a/YaeBlog/Services/BlogHotReloadService.cs b/src/YaeBlog/Services/BlogHotReloadService.cs
    similarity index 74%
    rename from YaeBlog/Services/BlogHotReloadService.cs
    rename to src/YaeBlog/Services/BlogHotReloadService.cs
    index 581c1b4..80d8c69 100644
    --- a/YaeBlog/Services/BlogHotReloadService.cs
    +++ b/src/YaeBlog/Services/BlogHotReloadService.cs
    @@ -16,11 +16,11 @@ public sealed class BlogHotReloadService(
     
             await rendererService.RenderAsync(true);
     
    -        Task[] reloadTasks = [FileWatchTask(stoppingToken)];
    +        Task[] reloadTasks = [WatchFileAsync(stoppingToken)];
             await Task.WhenAll(reloadTasks);
         }
     
    -    private async Task FileWatchTask(CancellationToken token)
    +    private async Task WatchFileAsync(CancellationToken token)
         {
             while (!token.IsCancellationRequested)
             {
    @@ -33,6 +33,15 @@ public sealed class BlogHotReloadService(
                     break;
                 }
     
    +            FileInfo changeFileInfo = new(changeFile);
    +
    +            if (changeFileInfo.Name.StartsWith('.'))
    +            {
    +                // Ignore dot-started file and directory.
    +                logger.LogDebug("Ignore hidden file: {}.", changeFile);
    +                continue;
    +            }
    +
                 logger.LogInformation("{} changed, re-rendering.", changeFile);
                 essayContentService.Clear();
                 await rendererService.RenderAsync(true);
    diff --git a/YaeBlog/Services/EssayContentService.cs b/src/YaeBlog/Services/EssayContentService.cs
    similarity index 100%
    rename from YaeBlog/Services/EssayContentService.cs
    rename to src/YaeBlog/Services/EssayContentService.cs
    diff --git a/src/YaeBlog/Services/EssayScanService.cs b/src/YaeBlog/Services/EssayScanService.cs
    new file mode 100644
    index 0000000..b35404a
    --- /dev/null
    +++ b/src/YaeBlog/Services/EssayScanService.cs
    @@ -0,0 +1,245 @@
    +using System.Collections.Concurrent;
    +using System.Text.RegularExpressions;
    +using Imageflow.Bindings;
    +using Imageflow.Fluent;
    +using Microsoft.Extensions.Options;
    +using YaeBlog.Abstraction;
    +using YaeBlog.Core.Exceptions;
    +using YaeBlog.Models;
    +using YamlDotNet.Core;
    +using YamlDotNet.Serialization;
    +
    +namespace YaeBlog.Services;
    +
    +public partial class EssayScanService : IEssayScanService
    +{
    +    private readonly BlogOptions _blogOptions;
    +    private readonly ISerializer _yamlSerializer;
    +    private readonly IDeserializer _yamlDeserializer;
    +    private readonly ILogger _logger;
    +
    +    public EssayScanService(ISerializer yamlSerializer,
    +        IDeserializer yamlDeserializer,
    +        IOptions blogOptions,
    +        ILogger logger)
    +    {
    +        _yamlSerializer = yamlSerializer;
    +        _yamlDeserializer = yamlDeserializer;
    +        _logger = logger;
    +        _blogOptions = blogOptions.Value;
    +        RootDirectory = ValidateRootDirectory();
    +    }
    +
    +    private DirectoryInfo RootDirectory { get; }
    +
    +    public async Task ScanContents()
    +    {
    +        ValidateDirectory(out DirectoryInfo drafts, out DirectoryInfo posts);
    +
    +        return new BlogContents(
    +            await ScanContentsInternal(drafts, true),
    +            await ScanContentsInternal(posts, false));
    +    }
    +
    +    public async Task SaveBlogContent(BlogContent content, bool isDraft = true)
    +    {
    +        ValidateDirectory(out DirectoryInfo drafts, out DirectoryInfo posts);
    +
    +        FileInfo targetFile = isDraft
    +            ? new FileInfo(Path.Combine(drafts.FullName, content.BlogName + ".md"))
    +            : new FileInfo(Path.Combine(posts.FullName, content.BlogName + ".md"));
    +
    +        if (targetFile.Exists)
    +        {
    +            _logger.LogWarning("Blog {} exists, overriding.", targetFile.Name);
    +        }
    +
    +        await using StreamWriter writer = targetFile.CreateText();
    +
    +        await writer.WriteAsync("---\n");
    +        await writer.WriteAsync(_yamlSerializer.Serialize(content.Metadata));
    +        await writer.WriteAsync("---\n");
    +
    +        if (string.IsNullOrEmpty(content.Content) && isDraft)
    +        {
    +            // 如果博客为操作且内容为空
    +            // 创建简介隔断符号
    +            await writer.WriteLineAsync("");
    +        }
    +        else
    +        {
    +            await writer.WriteAsync(content.Content);
    +        }
    +
    +        // 保存图片文件
    +        await Task.WhenAll(from image in content.Images
    +            select File.WriteAllBytesAsync(image.File.FullName, image.Content));
    +    }
    +
    +    private record struct BlogResult(
    +        FileInfo BlogFile,
    +        string BlogContent,
    +        List Images,
    +        List NotFoundImages);
    +
    +    private async Task> ScanContentsInternal(DirectoryInfo directory, bool isDraft)
    +    {
    +        // 扫描以md结尾且不是隐藏文件的文件
    +        IEnumerable markdownFiles = from file in directory.EnumerateFiles()
    +            where file.Extension == ".md" && !file.Name.StartsWith('.')
    +            select file;
    +
    +        ConcurrentBag fileContents = [];
    +
    +        await Parallel.ForEachAsync(markdownFiles, async (file, token) =>
    +        {
    +            using StreamReader reader = file.OpenText();
    +            string blogName = file.Name.Split('.')[0];
    +            string blogContent = await reader.ReadToEndAsync(token);
    +            ImageResult imageResult =
    +                await ScanImagePreBlog(directory, blogName,
    +                    blogContent);
    +
    +            fileContents.Add(new BlogResult(file, blogContent, imageResult.Images, imageResult.NotfoundImages));
    +        });
    +
    +        ConcurrentBag contents = [];
    +
    +        await Task.Run(() =>
    +        {
    +            foreach (BlogResult blog in fileContents)
    +            {
    +                if (blog.BlogContent.Length < 4)
    +                {
    +                    // Even not contains a legal header.
    +                    continue;
    +                }
    +
    +                int endPos = blog.BlogContent.IndexOf("---", 4, StringComparison.Ordinal);
    +                if (!blog.BlogContent.StartsWith("---") || endPos is -1 or 0)
    +                {
    +                    _logger.LogWarning("Failed to parse metadata from {}, skipped.", blog.BlogFile.Name);
    +                    return;
    +                }
    +
    +                string metadataString = blog.BlogContent[4..endPos];
    +
    +                try
    +                {
    +                    MarkdownMetadata metadata = _yamlDeserializer.Deserialize(metadataString);
    +                    _logger.LogDebug("Scan metadata title: '{title}' for {name}.", metadata.Title, blog.BlogFile.Name);
    +
    +                    contents.Add(new BlogContent(blog.BlogFile, metadata, blog.BlogContent[(endPos + 3)..], isDraft,
    +                        blog.Images, blog.NotFoundImages));
    +                }
    +                catch (YamlException e)
    +                {
    +                    _logger.LogWarning("Failed to parser metadata from {name} due to {exception}, skipping",
    +                        blog.BlogFile.Name, e);
    +                }
    +            }
    +        });
    +
    +        return contents;
    +    }
    +
    +    private record struct ImageResult(List Images, List NotfoundImages);
    +
    +    private async Task ScanImagePreBlog(DirectoryInfo directory, string blogName, string content)
    +    {
    +        DirectoryInfo imageDirectory = new(Path.Combine(directory.FullName, blogName));
    +
    +        Dictionary usedImages = imageDirectory.Exists
    +            ? imageDirectory.EnumerateFiles().ToDictionary(file => file.FullName, _ => false)
    +            : [];
    +        List notFoundImages = [];
    +
    +        // 同时扫描markdown格式和HTML格式的图片
    +        MatchCollection markdownMatchResult = MarkdownImagePattern.Matches(content);
    +        MatchCollection htmlMatchResult = HtmlImagePattern.Matches(content);
    +
    +        IEnumerable imageNames = from match in markdownMatchResult.Concat(htmlMatchResult)
    +            select match.Groups[1].Value;
    +
    +        foreach (string imageName in imageNames)
    +        {
    +            // 判断md文件中的图片名称中是否包含文件夹名称
    +            // 例如 blog-1/image.png 或者 image.png
    +            // 如果不带文件夹名称
    +            // 默认添加同博客名文件夹
    +            FileInfo usedFile = imageName.Contains(blogName)
    +                ? new FileInfo(Path.Combine(directory.FullName, imageName))
    +                : new FileInfo(Path.Combine(directory.FullName, blogName, imageName));
    +
    +            if (usedImages.TryGetValue(usedFile.FullName, out _))
    +            {
    +                usedImages[usedFile.FullName] = true;
    +            }
    +            else
    +            {
    +                notFoundImages.Add(usedFile);
    +            }
    +        }
    +
    +        List images = (await Task.WhenAll((from pair in usedImages
    +            select GetImageInfo(new FileInfo(pair.Key), pair.Value)).ToArray())).ToList();
    +
    +        return new ImageResult(images, notFoundImages);
    +    }
    +
    +    private static async Task GetImageInfo(FileInfo file, bool isUsed)
    +    {
    +        byte[] image = await File.ReadAllBytesAsync(file.FullName);
    +
    +        if (file.Extension is ".jpg" or ".jpeg" or ".png")
    +        {
    +            ImageInfo imageInfo =
    +                await ImageJob.GetImageInfoAsync(MemorySource.Borrow(image), SourceLifetime.NowOwnedAndDisposedByTask);
    +
    +            return new BlogImageInfo(file, imageInfo.ImageWidth, imageInfo.ImageWidth, imageInfo.PreferredMimeType,
    +                image, isUsed);
    +        }
    +
    +        return new BlogImageInfo(file, 0, 0, file.Extension switch
    +        {
    +            "svg" => "image/svg",
    +            "avif" => "image/avif",
    +            _ => string.Empty
    +        }, image, isUsed);
    +    }
    +
    +    [GeneratedRegex(@"\!\[.*?\]\((.*?)\)")]
    +    private static partial Regex MarkdownImagePattern { get; }
    +
    +    [GeneratedRegex("""]*?src\s*=\s*["']([^"']*)["'][^>]*>""")]
    +    private static partial Regex HtmlImagePattern { get; }
    +
    +
    +    private DirectoryInfo ValidateRootDirectory()
    +    {
    +        DirectoryInfo rootDirectory = new(Path.Combine(Environment.CurrentDirectory, _blogOptions.Root));
    +
    +        if (!rootDirectory.Exists)
    +        {
    +            throw new BlogFileException($"'{_blogOptions.Root}' is not a directory.");
    +        }
    +
    +        return rootDirectory;
    +    }
    +
    +    private void ValidateDirectory(out DirectoryInfo drafts, out DirectoryInfo posts)
    +    {
    +        if (RootDirectory.EnumerateDirectories().All(dir => dir.Name != "drafts"))
    +        {
    +            throw new BlogFileException($"'{_blogOptions.Root}/drafts' not exists.");
    +        }
    +
    +        if (RootDirectory.EnumerateDirectories().All(dir => dir.Name != "posts"))
    +        {
    +            throw new BlogFileException($"'{_blogOptions.Root}/posts' not exists.");
    +        }
    +
    +        drafts = new DirectoryInfo(Path.Combine(_blogOptions.Root, "drafts"));
    +        posts = new DirectoryInfo(Path.Combine(_blogOptions.Root, "posts"));
    +    }
    +}
    diff --git a/src/YaeBlog/Services/GitHeatMapService.cs b/src/YaeBlog/Services/GitHeatMapService.cs
    new file mode 100644
    index 0000000..eae9f55
    --- /dev/null
    +++ b/src/YaeBlog/Services/GitHeatMapService.cs
    @@ -0,0 +1,115 @@
    +using DotNext;
    +using Microsoft.Extensions.Options;
    +using YaeBlog.Extensions;
    +using YaeBlog.Models;
    +
    +namespace YaeBlog.Services;
    +
    +public sealed class GitHeapMapService(IServiceProvider serviceProvider, IOptions giteaOptions,
    +    ILogger logger)
    +{
    +    /// 
    +    /// 存储贡献列表
    +    /// 贡献列表采用懒加载和缓存机制,一天之内只请求一次Gitea服务器获得数据并缓存
    +    /// 
    +    private List _gitContributionsGroupedByWeek = [];
    +
    +    /// 
    +    /// 最后一次更新贡献列表的时间
    +    /// 
    +    private DateOnly _updateTime = DateOnly.MinValue;
    +
    +    public async Task> GetGitContributionGroupedByWeek()
    +    {
    +        DateOnly today = DateOnly.FromDateTime(DateTimeOffset.Now.DateTime);
    +        if (_updateTime == today)
    +        {
    +            logger.LogDebug("Git contribution grouped by week cache is hit.");
    +            return _gitContributionsGroupedByWeek;
    +        }
    +
    +        // 今天尚未更新
    +        // 更新一下
    +        GiteaFetchService giteaFetchService = serviceProvider.GetRequiredService();
    +        Result> r =
    +            await giteaFetchService.FetchGiteaContributions(giteaOptions.Value.HeatMapUsername);
    +
    +        if (!r.TryGet(out List? items))
    +        {
    +            logger.LogError("Failed to fetch heatmap data: {}", r.Error);
    +            return _gitContributionsGroupedByWeek;
    +        }
    +
    +        // The contribution is not grouped by day, so group them.
    +        IEnumerable groupedItems = items
    +            .GroupBy(i => i.Time)
    +            .Select(group => new GitContributionItem(group.Key,
    +                group.Select(i => i.ContributionCount).Sum()));
    +
    +        List result = new(52);
    +
    +        // Consider the input data is in order.
    +        // Start should be one year ago.
    +        GitContributionGroupedByWeek groupedContribution = new(DateOnly.Today.AddDays(-365 - 7).LastMonday, []);
    +        logger.LogDebug("Create new item group by week {}.", groupedContribution.Monday);
    +
    +        foreach ((DateOnly date, long contributions) in groupedItems)
    +        {
    +            DateOnly mondayOfItem = date.LastMonday;
    +            logger.LogDebug("Current date of item: {item}, monday is {monday}", date, mondayOfItem);
    +
    +            // If current item is in the same week of last item.
    +            if (mondayOfItem == groupedContribution.Monday)
    +            {
    +                // Fill the spacing of empty days with 0 contribution.
    +                FillSpacing(groupedContribution, date);
    +
    +                groupedContribution.Contributions.Add(new GitContributionItem(date, contributions));
    +                continue;
    +            }
    +
    +            // Current time is in the next (or much more) week of last item.
    +            // Fill the spacing, including the last week inner spacing and outer spacing.
    +            while (groupedContribution.Monday < mondayOfItem)
    +            {
    +                FillSpacing(groupedContribution, date);
    +                result.Add(groupedContribution);
    +                groupedContribution = new GitContributionGroupedByWeek(groupedContribution.Monday.AddDays(7), []);
    +                logger.LogDebug("Create new item group by week {}.", groupedContribution.Monday);
    +            }
    +
    +            // Now, the inner spacing of one week.
    +            FillSpacing(groupedContribution, date);
    +            groupedContribution.Contributions.Add(new GitContributionItem(date, contributions));
    +        }
    +
    +        // Not fill the last item and add directly.
    +        result.Add(groupedContribution);
    +
    +        _gitContributionsGroupedByWeek = result;
    +        _updateTime = DateOnly.Today;
    +
    +        return _gitContributionsGroupedByWeek;
    +    }
    +
    +    private static void FillSpacing(GitContributionGroupedByWeek contribution, in DateOnly date)
    +    {
    +        if (contribution.Monday == date)
    +        {
    +            return;
    +        }
    +
    +        if (contribution.Contributions.Count == 0)
    +        {
    +            contribution.Contributions.Add(new GitContributionItem(contribution.Monday, 0));
    +        }
    +
    +        DateOnly lastDate = contribution.Contributions.Last().Time;
    +        // The day in one week is 7, so th count of items of one week should not bigger than 7.
    +        while (contribution.Contributions.Count < 7 && lastDate < date.AddDays(-1))
    +        {
    +            lastDate = lastDate.AddDays(1);
    +            contribution.Contributions.Add(new GitContributionItem(lastDate, 0));
    +        }
    +    }
    +}
    diff --git a/src/YaeBlog/Services/GiteaFetchService.cs b/src/YaeBlog/Services/GiteaFetchService.cs
    new file mode 100644
    index 0000000..18ab068
    --- /dev/null
    +++ b/src/YaeBlog/Services/GiteaFetchService.cs
    @@ -0,0 +1,63 @@
    +using System.Net.Http.Headers;
    +using System.Text.Json;
    +using DotNext;
    +using Microsoft.Extensions.Options;
    +using YaeBlog.Core.Exceptions;
    +using YaeBlog.Models;
    +
    +namespace YaeBlog.Services;
    +
    +public sealed class GiteaFetchService
    +{
    +    private readonly HttpClient _httpClient;
    +
    +    private static readonly JsonSerializerOptions s_serializerOptions = new()
    +    {
    +        PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
    +        RespectRequiredConstructorParameters = true, RespectNullableAnnotations = true
    +    };
    +
    +    /// 
    +    /// For test only.
    +    /// 
    +    internal GiteaFetchService(IOptions giteaOptions, HttpClient httpClient)
    +    {
    +        _httpClient = httpClient;
    +
    +        _httpClient.BaseAddress = new Uri(giteaOptions.Value.BaseAddress);
    +        _httpClient.DefaultRequestHeaders.Authorization =
    +            new AuthenticationHeaderValue("Bearer", giteaOptions.Value.ApiKey);
    +    }
    +
    +    public GiteaFetchService(IOptions giteaOptions, IHttpClientFactory httpClientFactory) : this(
    +        giteaOptions, httpClientFactory.CreateClient())
    +    {
    +    }
    +
    +    private record UserHeatmapData(long Contributions, long Timestamp);
    +
    +    public async Task>> FetchGiteaContributions(string username)
    +    {
    +        try
    +        {
    +            List? data =
    +                await _httpClient.GetFromJsonAsync>($"users/{username}/heatmap",
    +                    s_serializerOptions);
    +
    +            if (data is null or { Count: 0 })
    +            {
    +                return Result.FromException>(
    +                    new GiteaFetchException("Failed to fetch valid data."));
    +            }
    +
    +            return Result.FromValue(data.Select(i =>
    +                new GitContributionItem(DateOnly.FromDateTime(DateTimeOffset.FromUnixTimeSeconds(i.Timestamp).DateTime),
    +                    i.Contributions)).ToList());
    +        }
    +        catch (HttpRequestException exception)
    +        {
    +            return Result.FromException>(new GiteaFetchException("Failed to fetch.",
    +                exception));
    +        }
    +    }
    +}
    diff --git a/src/YaeBlog/Services/ImageCompressService.cs b/src/YaeBlog/Services/ImageCompressService.cs
    new file mode 100644
    index 0000000..831e230
    --- /dev/null
    +++ b/src/YaeBlog/Services/ImageCompressService.cs
    @@ -0,0 +1,119 @@
    +using Imageflow.Fluent;
    +using YaeBlog.Abstraction;
    +using YaeBlog.Core.Exceptions;
    +using YaeBlog.Models;
    +
    +namespace YaeBlog.Services;
    +
    +public sealed class ImageCompressService(IEssayScanService essayScanService, ILogger logger)
    +{
    +    private record struct CompressResult(BlogImageInfo ImageInfo, byte[] CompressContent);
    +
    +    public async Task> ScanUsedImages()
    +    {
    +        BlogContents contents = await essayScanService.ScanContents();
    +        List originalImages = (from content in contents.Posts.Concat(contents.Drafts)
    +            from image in content.Images
    +            where image.IsUsed
    +            select image).ToList();
    +
    +        originalImages.Sort();
    +
    +        return originalImages;
    +    }
    +
    +    public async Task Compress(bool dryRun)
    +    {
    +        BlogContents contents = await essayScanService.ScanContents();
    +
    +        // 筛选需要压缩的图片
    +        // 即图片被博客使用且是jpeg/png格式
    +        List needCompressContents = (from content in contents
    +            where content.Images.Any(i => i is { IsUsed: true } and { File.Extension: ".jpg" or ".jpeg" or ".png" })
    +            select content).ToList();
    +
    +        if (needCompressContents.Count == 0)
    +        {
    +            return;
    +        }
    +
    +        int uncompressedSize = 0;
    +        int compressedSize = 0;
    +        List compressedContent = new(needCompressContents.Count);
    +
    +        foreach (BlogContent content in needCompressContents)
    +        {
    +            List uncompressedImages = (from image in content.Images
    +                where image is { IsUsed: true } and { File.Extension: ".jpg" or ".jpeg" or ".png" }
    +                select image).ToList();
    +
    +            uncompressedSize += uncompressedImages.Select(i => i.Size).Sum();
    +
    +            foreach (BlogImageInfo image in uncompressedImages)
    +            {
    +                logger.LogInformation("Uncompressed image: {} belonging to blog {}.", image.File.Name,
    +                    content.BlogName);
    +            }
    +
    +            CompressResult[] compressedImages = (await Task.WhenAll(from image in uncompressedImages
    +                select Task.Run(async () => new CompressResult(image, await ConvertToWebp(image))))).ToArray();
    +
    +            compressedSize += compressedImages.Select(i => i.CompressContent.Length).Sum();
    +
    +            // 直接在原有的图片列表上添加图片
    +            List images = content.Images.Concat(from r in compressedImages
    +                select r.ImageInfo with
    +                {
    +                    File = new FileInfo(r.ImageInfo.File.FullName.Split('.')[0] + ".webp"),
    +                    Content = r.CompressContent,
    +                    MineType = "image/webp"
    +                }).ToList();
    +            // 修改文本
    +            string blogContent = compressedImages.Aggregate(content.Content, (c, r) =>
    +            {
    +                string originalName = r.ImageInfo.File.Name;
    +                string outputName = originalName.Split('.')[0] + ".webp";
    +
    +                return c.Replace(originalName, outputName);
    +            });
    +
    +            compressedContent.Add(content with { Images = images, Content = blogContent });
    +        }
    +
    +        logger.LogInformation("Compression ratio: {}%.", (double)compressedSize / uncompressedSize * 100.0);
    +
    +        if (dryRun is false)
    +        {
    +            await Task.WhenAll(from content in compressedContent
    +                select essayScanService.SaveBlogContent(content, content.IsDraft));
    +        }
    +    }
    +
    +    private static async Task ConvertToWebp(BlogImageInfo image)
    +    {
    +        using ImageJob job = new();
    +        BuildJobResult result = await job.Decode(MemorySource.Borrow(image.Content))
    +            .Branch(f => f.EncodeToBytes(new WebPLosslessEncoder()))
    +            .EncodeToBytes(new WebPLossyEncoder(75))
    +            .Finish()
    +            .InProcessAsync();
    +
    +        // 超过128KB的图片使用有损压缩
    +        // 反之使用无损压缩
    +
    +        ArraySegment? losslessImage = result.TryGet(1)?.TryGetBytes();
    +        ArraySegment? lossyImage = result.TryGet(2)?.TryGetBytes();
    +
    +        if (image.Size <= 128 * 1024 && losslessImage.HasValue)
    +        {
    +            return losslessImage.Value.ToArray();
    +        }
    +
    +        if (lossyImage.HasValue)
    +        {
    +            return lossyImage.Value.ToArray();
    +        }
    +
    +        throw new BlogCommandException($"Failed to convert {image.File.Name} to webp format: return value is null.");
    +    }
    +}
    diff --git a/src/YaeBlog/Services/MarkdownWordCounter.cs b/src/YaeBlog/Services/MarkdownWordCounter.cs
    new file mode 100644
    index 0000000..b5d3a84
    --- /dev/null
    +++ b/src/YaeBlog/Services/MarkdownWordCounter.cs
    @@ -0,0 +1,62 @@
    +using YaeBlog.Extensions;
    +using YaeBlog.Models;
    +
    +namespace YaeBlog.Services
    +{
    +    public class MarkdownWordCounter
    +    {
    +        private bool _inCodeBlock;
    +        private int _index;
    +        private readonly string _content;
    +
    +        private uint WordCount { get; set; }
    +
    +        private MarkdownWordCounter(BlogContent content)
    +        {
    +            _content = content.Content;
    +        }
    +
    +        private void CountWordInner()
    +        {
    +            while (_index < _content.Length)
    +            {
    +                if (IsCodeBlockTag())
    +                {
    +                    _inCodeBlock = !_inCodeBlock;
    +                }
    +
    +                if (!_inCodeBlock && char.IsLetterOrDigit(_content, _index))
    +                {
    +                    WordCount += 1;
    +                }
    +
    +                _index++;
    +            }
    +        }
    +
    +        private bool IsCodeBlockTag()
    +        {
    +            // 首先考虑识别代码块
    +            bool outerCodeBlock =
    +                Enumerable.Range(0, 3)
    +                .Select(i => _index + i < _content.Length && _content.AsSpan().Slice(_index + i, 1) is "`")
    +                .All(i => i);
    +
    +            if (outerCodeBlock)
    +            {
    +                return true;
    +            }
    +
    +            // 然后识别行内代码
    +            return _index < _content.Length && _content.AsSpan().Slice(_index, 1) is "`";
    +        }
    +
    +        public static uint CountWord(BlogContent content)
    +        {
    +            MarkdownWordCounter counter = new(content);
    +            counter.CountWordInner();
    +
    +            return counter.WordCount;
    +        }
    +    }
    +}
    diff --git a/YaeBlog/Services/RendererService.cs b/src/YaeBlog/Services/RendererService.cs
    similarity index 69%
    rename from YaeBlog/Services/RendererService.cs
    rename to src/YaeBlog/Services/RendererService.cs
    index dda44ee..a4054d9 100644
    --- a/YaeBlog/Services/RendererService.cs
    +++ b/src/YaeBlog/Services/RendererService.cs
    @@ -9,7 +9,7 @@ using YaeBlog.Models;
     
     namespace YaeBlog.Services;
     
    -public partial class RendererService(
    +public sealed partial class RendererService(
         ILogger logger,
         IEssayScanService essayScanService,
         MarkdownPipeline markdownPipeline,
    @@ -34,42 +34,32 @@ public partial class RendererService(
             }
     
             IEnumerable preProcessedContents = await PreProcess(posts);
    +        ConcurrentBag essays = [];
     
    -        List essays = [];
    -        foreach (BlogContent content in preProcessedContents)
    +        Parallel.ForEach(preProcessedContents, content =>
             {
    -            uint wordCount = GetWordCount(content);
    -            BlogEssay essay = new()
    -            {
    -                Title = content.Metadata.Title ?? content.FileName,
    -                FileName = content.FileName,
    -                IsDraft = content.IsDraft,
    -                Description = GetDescription(content),
    -                WordCount = wordCount,
    -                ReadTime = CalculateReadTime(wordCount),
    -                PublishTime = content.Metadata.Date ?? DateTime.Now,
    -                HtmlContent = content.FileContent
    -            };
    +            (uint wordCount, string readTime) = GetWordCount(content);
    +            DateTimeOffset publishDate = content.Metadata.Date is null
    +                ? DateTimeOffset.Now
    +                : DateTimeOffset.Parse(content.Metadata.Date);
    +            // 如果不存在最后的更新时间,就把更新时间设置为发布时间
    +            DateTimeOffset updateTime = content.Metadata.UpdateTime is null
    +                ? publishDate
    +                : DateTimeOffset.Parse(content.Metadata.UpdateTime);
    +            string description = GetDescription(content);
    +            List tags = content.Metadata.Tags ?? [];
     
    -            if (content.Metadata.Tags is not null)
    -            {
    -                essay.Tags.AddRange(content.Metadata.Tags);
    -            }
    +            string originalHtml = Markdown.ToHtml(content.Content, markdownPipeline);
    +
    +            BlogEssay essay = new(
    +                content.Metadata.Title ?? content.BlogName, content.BlogName, content.IsDraft, publishDate, updateTime,
    +                description, wordCount, readTime, tags, originalHtml);
    +            logger.LogDebug("Render essay: {}", essay);
     
                 essays.Add(essay);
    -        }
    -
    -        ConcurrentBag postProcessEssays = [];
    -        Parallel.ForEach(essays, essay =>
    -        {
    -            BlogEssay newEssay =
    -                essay.WithNewHtmlContent(Markdown.ToHtml(essay.HtmlContent, markdownPipeline));
    -
    -            postProcessEssays.Add(newEssay);
    -            logger.LogDebug("Render markdown file {}.", newEssay);
             });
     
    -        IEnumerable postProcessedEssays = await PostProcess(postProcessEssays);
    +        IEnumerable postProcessedEssays = await PostProcess(essays);
     
             foreach (BlogEssay essay in postProcessedEssays)
             {
    @@ -156,17 +146,17 @@ public partial class RendererService(
         private string GetDescription(BlogContent content)
         {
             const string delimiter = "";
    -        int pos = content.FileContent.IndexOf(delimiter, StringComparison.Ordinal);
    +        int pos = content.Content.IndexOf(delimiter, StringComparison.Ordinal);
             bool breakSentence = false;
     
             if (pos == -1)
             {
                 // 自动截取前50个字符
    -            pos = content.FileContent.Length < 50 ? content.FileContent.Length : 50;
    +            pos = content.Content.Length < 50 ? content.Content.Length : 50;
                 breakSentence = true;
             }
     
    -        string rawContent = content.FileContent[..pos];
    +        string rawContent = content.Content[..pos];
             MatchCollection matches = DescriptionPattern.Matches(rawContent);
     
             StringBuilder builder = new();
    @@ -182,28 +172,21 @@ public partial class RendererService(
     
             string description = builder.ToString();
     
    -        logger.LogDebug("Description of {} is {}.", content.FileName,
    +        logger.LogDebug("Description of {name} is {desc}.", content.BlogName,
                 description);
             return description;
         }
     
    -    private uint GetWordCount(BlogContent content)
    +    private (uint, string) GetWordCount(BlogContent content)
         {
    -        int count = (from c in content.FileContent
    -            where char.IsLetterOrDigit(c)
    -            select c).Count();
    +        uint count = MarkdownWordCounter.CountWord(content);
     
    -        logger.LogDebug("Word count of {} is {}", content.FileName,
    +        logger.LogDebug("Word count of {blog} is {count}", content.BlogName,
                 count);
    -        return (uint)count;
    -    }
    -
    -    private static string CalculateReadTime(uint wordCount)
    -    {
             // 据说语文教学大纲规定,中国高中生阅读现代文的速度是600字每分钟
    -        int second = (int)wordCount / 10;
    -        TimeSpan span = new(0, 0, second);
    +        uint second = count / 10;
    +        TimeSpan span = new(0, 0, (int)second);
     
    -        return span.ToString("mm'分 'ss'秒'");
    +        return (count, span.ToString("mm'分'ss'秒'"));
         }
     }
    diff --git a/src/YaeBlog/Services/YaeCommandService.cs b/src/YaeBlog/Services/YaeCommandService.cs
    new file mode 100644
    index 0000000..e4dd2ed
    --- /dev/null
    +++ b/src/YaeBlog/Services/YaeCommandService.cs
    @@ -0,0 +1,281 @@
    +using System.CommandLine;
    +using System.CommandLine.Invocation;
    +using System.Text;
    +using Microsoft.Extensions.Options;
    +using YaeBlog.Abstraction;
    +using YaeBlog.Core.Exceptions;
    +using YaeBlog.Models;
    +
    +namespace YaeBlog.Services;
    +
    +public class YaeCommandService(
    +    string[] arguments,
    +    IEssayScanService essayScanService,
    +    IServiceProvider serviceProvider,
    +    IOptions blogOptions,
    +    ILogger logger,
    +    IHostApplicationLifetime applicationLifetime)
    +    : IHostedService
    +{
    +    private readonly BlogOptions _blogOptions = blogOptions.Value;
    +    private bool _oneShotCommandFlag = true;
    +
    +    public async Task StartAsync(CancellationToken cancellationToken)
    +    {
    +        RootCommand rootCommand = new("YaeBlog CLI");
    +
    +        RegisterServeCommand(rootCommand);
    +        RegisterWatchCommand(rootCommand, cancellationToken);
    +
    +        RegisterNewCommand(rootCommand);
    +        RegisterUpdateCommand(rootCommand);
    +        RegisterScanCommand(rootCommand);
    +        RegisterPublishCommand(rootCommand);
    +        RegisterCompressCommand(rootCommand);
    +
    +        int exitCode = await rootCommand.InvokeAsync(arguments);
    +
    +        if (exitCode != 0)
    +        {
    +            throw new BlogCommandException($"YaeBlog command exited with no-zero code {exitCode}");
    +        }
    +
    +        if (_oneShotCommandFlag)
    +        {
    +            applicationLifetime.StopApplication();
    +        }
    +    }
    +
    +    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
    +
    +    private void RegisterServeCommand(RootCommand rootCommand)
    +    {
    +        Command command = new("serve", "Start http server.");
    +        rootCommand.AddCommand(command);
    +
    +        command.SetHandler(HandleServeCommand);
    +
    +        // When invoking the root command without sub command, fallback to serve command.
    +        rootCommand.SetHandler(HandleServeCommand);
    +    }
    +
    +    private async Task HandleServeCommand(InvocationContext context)
    +    {
    +        _oneShotCommandFlag = false;
    +
    +        logger.LogInformation("Failed to load cache, re-render essays.");
    +        RendererService rendererService = serviceProvider.GetRequiredService();
    +        await rendererService.RenderAsync();
    +    }
    +
    +    private void RegisterWatchCommand(RootCommand rootCommand, CancellationToken cancellationToken)
    +    {
    +        Command command = new("watch", "Start a blog watcher that re-render when file changes.");
    +        rootCommand.AddCommand(command);
    +
    +        command.SetHandler(async _ =>
    +        {
    +            _oneShotCommandFlag = false;
    +
    +            // BlogHotReloadService is derived from BackgroundService, but we do not let framework trigger it.
    +            BlogHotReloadService blogHotReloadService = serviceProvider.GetRequiredService();
    +            await blogHotReloadService.StartAsync(cancellationToken);
    +        });
    +    }
    +
    +    private void RegisterNewCommand(RootCommand rootCommand)
    +    {
    +        Command command = new("new", "Create a new blog file and image directory.");
    +        rootCommand.AddCommand(command);
    +
    +        Argument filenameArgument = new(name: "blog name", description: "The created blog filename.");
    +        command.AddArgument(filenameArgument);
    +
    +        command.SetHandler(HandleNewCommand, filenameArgument);
    +    }
    +
    +    private async Task HandleNewCommand(string filename)
    +    {
    +        BlogContents contents = await essayScanService.ScanContents();
    +
    +        if (contents.Posts.Any(content => content.BlogName == filename))
    +        {
    +            throw new BlogCommandException("There exits the same title blog in posts.");
    +        }
    +
    +        await essayScanService.SaveBlogContent(new BlogContent(
    +            new FileInfo(Path.Combine(_blogOptions.Root, "drafts", filename + ".md")),
    +            new MarkdownMetadata
    +            {
    +                Title = filename,
    +                Date = DateTimeOffset.Now.ToString("o"),
    +                UpdateTime = DateTimeOffset.Now.ToString("o")
    +            },
    +            string.Empty, true, [], []
    +        ));
    +
    +        logger.LogInformation("Create new blog '{}'", filename);
    +    }
    +
    +    private void RegisterUpdateCommand(RootCommand rootCommand)
    +    {
    +        Command command = new("update", "Update the blog essay.");
    +        rootCommand.AddCommand(command);
    +
    +        Argument filenameArgument = new(name: "blog name", description: "The blog filename to update.");
    +        command.AddArgument(filenameArgument);
    +
    +        command.SetHandler(HandleUpdateCommand, filenameArgument);
    +    }
    +
    +    private async Task HandleUpdateCommand(string filename)
    +    {
    +        logger.LogInformation("The update command only considers published blogs.");
    +        BlogContents contents = await essayScanService.ScanContents();
    +
    +        BlogContent? content = contents.Posts.FirstOrDefault(c => c.BlogName == filename);
    +        if (content is null)
    +        {
    +            throw new BlogCommandException($"Target essay {filename} is not exist.");
    +        }
    +
    +        content.Metadata.UpdateTime = DateTimeOffset.Now.ToString("o");
    +        await essayScanService.SaveBlogContent(content, content.IsDraft);
    +        logger.LogInformation("Update time of essay '{}' updated.", content.BlogName);
    +    }
    +
    +    private void RegisterScanCommand(RootCommand rootCommand)
    +    {
    +        Command command = new("scan", "Scan unused and not found images.");
    +        rootCommand.AddCommand(command);
    +
    +        Option removeOption =
    +            new(name: "--rm", description: "Remove unused images.", getDefaultValue: () => false);
    +        command.AddOption(removeOption);
    +
    +        command.SetHandler(HandleScanCommand, removeOption);
    +    }
    +
    +    private async Task HandleScanCommand(bool removeUnusedImages)
    +    {
    +        BlogContents contents = await essayScanService.ScanContents();
    +        List unusedImages = (from content in contents
    +            from image in content.Images
    +            where image is { IsUsed: false }
    +            select image).ToList();
    +
    +        if (unusedImages.Count != 0)
    +        {
    +            StringBuilder builder = new();
    +            builder.Append("Found unused images:").Append('\n');
    +
    +            foreach (BlogImageInfo image in unusedImages)
    +            {
    +                builder.Append('\t').Append("- ").Append(image.File.FullName).Append('\n');
    +            }
    +
    +            logger.LogInformation("{}", builder.ToString());
    +            logger.LogInformation("HINT: use '--rm' to remove unused images.");
    +        }
    +
    +        if (removeUnusedImages)
    +        {
    +            foreach (BlogImageInfo image in unusedImages)
    +            {
    +                image.File.Delete();
    +            }
    +        }
    +
    +        StringBuilder infoBuilder = new();
    +        infoBuilder.Append("Used not existed images:\n");
    +
    +        bool flag = false;
    +        foreach (BlogContent content in contents)
    +        {
    +            foreach (FileInfo file in content.NotfoundImages)
    +            {
    +                flag = true;
    +                infoBuilder.Append('\t').Append("- ").Append(file.Name).Append(" in ").Append(content.BlogName)
    +                    .Append('\n');
    +            }
    +        }
    +
    +        if (flag)
    +        {
    +            logger.LogInformation("{}", infoBuilder.ToString());
    +        }
    +    }
    +
    +    private void RegisterPublishCommand(RootCommand rootCommand)
    +    {
    +        Command command = new("publish", "Publish a new blog file.");
    +        rootCommand.AddCommand(command);
    +
    +        Argument filenameArgument = new(name: "blog name", description: "The published blog filename.");
    +        command.AddArgument(filenameArgument);
    +
    +        command.SetHandler(HandlePublishCommand, filenameArgument);
    +    }
    +
    +    private async Task HandlePublishCommand(string filename)
    +    {
    +        BlogContents contents = await essayScanService.ScanContents();
    +
    +        BlogContent? content = (from blog in contents.Drafts
    +            where blog.BlogName == filename
    +            select blog).FirstOrDefault();
    +
    +        if (content is null)
    +        {
    +            throw new BlogCommandException("Target blog doest not exist.");
    +        }
    +
    +        logger.LogInformation("Publish blog {}", content.BlogName);
    +
    +        // 设置发布的时间
    +        content.Metadata.Date = DateTimeOffset.Now.ToString("o");
    +        content.Metadata.UpdateTime = DateTimeOffset.Now.ToString("o");
    +
    +        // 将选中的博客文件复制到posts
    +        await essayScanService.SaveBlogContent(content, isDraft: false);
    +
    +        // 复制图片文件夹
    +        DirectoryInfo sourceImageDirectory =
    +            new(Path.Combine(blogOptions.Value.Root, "drafts", content.BlogName));
    +        DirectoryInfo targetImageDirectory =
    +            new(Path.Combine(blogOptions.Value.Root, "posts", content.BlogName));
    +
    +        if (sourceImageDirectory.Exists)
    +        {
    +            targetImageDirectory.Create();
    +            foreach (FileInfo file in sourceImageDirectory.EnumerateFiles())
    +            {
    +                file.CopyTo(Path.Combine(targetImageDirectory.FullName, file.Name), true);
    +            }
    +
    +            sourceImageDirectory.Delete(true);
    +        }
    +
    +        // 删除原始的文件
    +        FileInfo sourceBlogFile = new(Path.Combine(blogOptions.Value.Root, "drafts", content.BlogName + ".md"));
    +        sourceBlogFile.Delete();
    +    }
    +
    +    private void RegisterCompressCommand(RootCommand rootCommand)
    +    {
    +        Command command = new("compress", "Compress png/jpeg image to webp image to reduce size.");
    +        rootCommand.Add(command);
    +
    +        Option dryRunOption = new("--dry-run", description: "Dry run the compression task but not write.",
    +            getDefaultValue: () => false);
    +        command.AddOption(dryRunOption);
    +
    +        command.SetHandler(HandleCompressCommand, dryRunOption);
    +    }
    +
    +    private async Task HandleCompressCommand(bool dryRun)
    +    {
    +        ImageCompressService imageCompressService = serviceProvider.GetRequiredService();
    +        await imageCompressService.Compress(dryRun);
    +    }
    +}
    diff --git a/src/YaeBlog/YaeBlog.csproj b/src/YaeBlog/YaeBlog.csproj
    new file mode 100644
    index 0000000..645a443
    --- /dev/null
    +++ b/src/YaeBlog/YaeBlog.csproj
    @@ -0,0 +1,32 @@
    +
    +
    +  
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +    
    +  
    +
    +  
    +    
    +  
    +
    +  
    +    
    +  
    +
    +  
    +    net10.0
    +    enable
    +    enable
    +  
    +
    +  
    +    pnpm install
    +    pwsh tailwind.ps1
    +  
    +
    diff --git a/src/YaeBlog/appsettings.json b/src/YaeBlog/appsettings.json
    new file mode 100644
    index 0000000..d0fe6e4
    --- /dev/null
    +++ b/src/YaeBlog/appsettings.json
    @@ -0,0 +1,44 @@
    +{
    +  "Logging": {
    +    "LogLevel": {
    +      "Default": "Information",
    +      "Microsoft.AspNetCore": "Warning",
    +      "System.Net.Http.HttpClient": "Warning"
    +    }
    +  },
    +  "AllowedHosts": "*",
    +  "Tailwind": {
    +    "InputFile": "wwwroot/input.css",
    +    "OutputFile": "wwwroot/output.css"
    +  },
    +  "Blog": {
    +    "Root": "../../source",
    +    "Announcement": "博客锐意装修中,敬请期待!测试阶段如有问题还请海涵。",
    +    "StartYear": 2021,
    +    "Links": [
    +      {
    +        "Name": "Ichirinko",
    +        "Description": "黑历史集合地,naive的代价",
    +        "Link": "https://ichirinko.top",
    +        "AvatarImage": "https://ichirinko-blog-img-1.oss-cn-shenzhen.aliyuncs.com/Pic_res/img/202209122110798.png"
    +      },
    +      {
    +        "Name": "不会写程序的晨旭",
    +        "Description": "一个普通大学生",
    +        "Link": "https://chenxutalk.top",
    +        "AvatarImage": "https://www.chenxutalk.top/img/photo.png"
    +      },
    +      {
    +        "Name": "万木长风",
    +        "Description": "世界渲染中...",
    +        "Link": "https://ryohai.fun",
    +        "AvatarImage": "https://ryohai.fun/static/favicons/favicon-32x32.png"
    +      }
    +    ]
    +  },
    +  "Gitea": {
    +    "BaseAddress": "https://git.rrricardo.top/api/v1/",
    +    "ApiKey": "7e33617e5d084199332fceec3e0cb04c6ddced55",
    +    "HeatMapUsername": "jackfiled"
    +  }
    +}
    diff --git a/src/YaeBlog/docker-compose.yaml b/src/YaeBlog/docker-compose.yaml
    new file mode 100644
    index 0000000..bc985dd
    --- /dev/null
    +++ b/src/YaeBlog/docker-compose.yaml
    @@ -0,0 +1,13 @@
    +version: '3.8'
    +
    +services:
    +  blog:
    +    image: registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest
    +    restart: unless-stopped
    +    labels:
    +      - "traefik.enable=true"
    +      - "traefik.http.routers.blog.rule=Host(`rrricardo.top`) || Host(`www.rrricardo.top`)"
    +      - "traefik.http.services.blog.loadbalancer.server.port=8080"
    +      - "traefik.http.routers.blog.tls=true"
    +      - "traefik.http.routers.blog.tls.certresolver=myresolver"
    +      - "com.centurylinklabs.watchtower.enable=true"
    diff --git a/src/YaeBlog/package.json b/src/YaeBlog/package.json
    new file mode 100644
    index 0000000..1e0009f
    --- /dev/null
    +++ b/src/YaeBlog/package.json
    @@ -0,0 +1,15 @@
    +{
    +    "name": "yae-blog",
    +    "version": "1.0.0",
    +    "description": "",
    +    "scripts": {
    +        "dev": "tailwindcss -i wwwroot/tailwind.css -o wwwroot/tailwind.g.css -w"
    +    },
    +    "keywords": [],
    +    "author": "",
    +    "license": "ISC",
    +    "devDependencies": {
    +        "tailwindcss": "^4.0.0",
    +        "@tailwindcss/cli": "^4.0.0"
    +    }
    +}
    diff --git a/src/YaeBlog/pnpm-lock.yaml b/src/YaeBlog/pnpm-lock.yaml
    new file mode 100644
    index 0000000..725fd8a
    --- /dev/null
    +++ b/src/YaeBlog/pnpm-lock.yaml
    @@ -0,0 +1,545 @@
    +lockfileVersion: '9.0'
    +
    +settings:
    +  autoInstallPeers: true
    +  excludeLinksFromLockfile: false
    +
    +importers:
    +
    +  .:
    +    devDependencies:
    +      '@tailwindcss/cli':
    +        specifier: ^4.0.0
    +        version: 4.0.15
    +      tailwindcss:
    +        specifier: ^4.0.0
    +        version: 4.0.15
    +
    +packages:
    +
    +  '@parcel/watcher-android-arm64@2.5.1':
    +    resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [arm64]
    +    os: [android]
    +
    +  '@parcel/watcher-darwin-arm64@2.5.1':
    +    resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [arm64]
    +    os: [darwin]
    +
    +  '@parcel/watcher-darwin-x64@2.5.1':
    +    resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [x64]
    +    os: [darwin]
    +
    +  '@parcel/watcher-freebsd-x64@2.5.1':
    +    resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [x64]
    +    os: [freebsd]
    +
    +  '@parcel/watcher-linux-arm-glibc@2.5.1':
    +    resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [arm]
    +    os: [linux]
    +    libc: [glibc]
    +
    +  '@parcel/watcher-linux-arm-musl@2.5.1':
    +    resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [arm]
    +    os: [linux]
    +    libc: [musl]
    +
    +  '@parcel/watcher-linux-arm64-glibc@2.5.1':
    +    resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [arm64]
    +    os: [linux]
    +    libc: [glibc]
    +
    +  '@parcel/watcher-linux-arm64-musl@2.5.1':
    +    resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [arm64]
    +    os: [linux]
    +    libc: [musl]
    +
    +  '@parcel/watcher-linux-x64-glibc@2.5.1':
    +    resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [x64]
    +    os: [linux]
    +    libc: [glibc]
    +
    +  '@parcel/watcher-linux-x64-musl@2.5.1':
    +    resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [x64]
    +    os: [linux]
    +    libc: [musl]
    +
    +  '@parcel/watcher-win32-arm64@2.5.1':
    +    resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [arm64]
    +    os: [win32]
    +
    +  '@parcel/watcher-win32-ia32@2.5.1':
    +    resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [ia32]
    +    os: [win32]
    +
    +  '@parcel/watcher-win32-x64@2.5.1':
    +    resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
    +    engines: {node: '>= 10.0.0'}
    +    cpu: [x64]
    +    os: [win32]
    +
    +  '@parcel/watcher@2.5.1':
    +    resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
    +    engines: {node: '>= 10.0.0'}
    +
    +  '@tailwindcss/cli@4.0.15':
    +    resolution: {integrity: sha512-52RdNZCpij4O8+25N9sfWZPG124e6ahmIS1uMHcJrdw10UdpPUFgSJtyMwf7COVOnkx0nkXfmp8CcYomPCrQ1Q==}
    +    hasBin: true
    +
    +  '@tailwindcss/node@4.0.15':
    +    resolution: {integrity: sha512-IODaJjNmiasfZX3IoS+4Em3iu0fD2HS0/tgrnkYfW4hyUor01Smnr5eY3jc4rRgaTDrJlDmBTHbFO0ETTDaxWA==}
    +
    +  '@tailwindcss/oxide-android-arm64@4.0.15':
    +    resolution: {integrity: sha512-EBuyfSKkom7N+CB3A+7c0m4+qzKuiN0WCvzPvj5ZoRu4NlQadg/mthc1tl5k9b5ffRGsbDvP4k21azU4VwVk3Q==}
    +    engines: {node: '>= 10'}
    +    cpu: [arm64]
    +    os: [android]
    +
    +  '@tailwindcss/oxide-darwin-arm64@4.0.15':
    +    resolution: {integrity: sha512-ObVAnEpLepMhV9VoO0JSit66jiN5C4YCqW3TflsE9boo2Z7FIjV80RFbgeL2opBhtxbaNEDa6D0/hq/EP03kgQ==}
    +    engines: {node: '>= 10'}
    +    cpu: [arm64]
    +    os: [darwin]
    +
    +  '@tailwindcss/oxide-darwin-x64@4.0.15':
    +    resolution: {integrity: sha512-IElwoFhUinOr9MyKmGTPNi1Rwdh68JReFgYWibPWTGuevkHkLWKEflZc2jtI5lWZ5U9JjUnUfnY43I4fEXrc4g==}
    +    engines: {node: '>= 10'}
    +    cpu: [x64]
    +    os: [darwin]
    +
    +  '@tailwindcss/oxide-freebsd-x64@4.0.15':
    +    resolution: {integrity: sha512-6BLLqyx7SIYRBOnTZ8wgfXANLJV5TQd3PevRJZp0vn42eO58A2LykRKdvL1qyPfdpmEVtF+uVOEZ4QTMqDRAWA==}
    +    engines: {node: '>= 10'}
    +    cpu: [x64]
    +    os: [freebsd]
    +
    +  '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.15':
    +    resolution: {integrity: sha512-Zy63EVqO9241Pfg6G0IlRIWyY5vNcWrL5dd2WAKVJZRQVeolXEf1KfjkyeAAlErDj72cnyXObEZjMoPEKHpdNw==}
    +    engines: {node: '>= 10'}
    +    cpu: [arm]
    +    os: [linux]
    +
    +  '@tailwindcss/oxide-linux-arm64-gnu@4.0.15':
    +    resolution: {integrity: sha512-2NemGQeaTbtIp1Z2wyerbVEJZTkAWhMDOhhR5z/zJ75yMNf8yLnE+sAlyf6yGDNr+1RqvWrRhhCFt7i0CIxe4Q==}
    +    engines: {node: '>= 10'}
    +    cpu: [arm64]
    +    os: [linux]
    +    libc: [glibc]
    +
    +  '@tailwindcss/oxide-linux-arm64-musl@4.0.15':
    +    resolution: {integrity: sha512-342GVnhH/6PkVgKtEzvNVuQ4D+Q7B7qplvuH20Cfz9qEtydG6IQczTZ5IT4JPlh931MG1NUCVxg+CIorr1WJyw==}
    +    engines: {node: '>= 10'}
    +    cpu: [arm64]
    +    os: [linux]
    +    libc: [musl]
    +
    +  '@tailwindcss/oxide-linux-x64-gnu@4.0.15':
    +    resolution: {integrity: sha512-g76GxlKH124RuGqacCEFc2nbzRl7bBrlC8qDQMiUABkiifDRHOIUjgKbLNG4RuR9hQAD/MKsqZ7A8L08zsoBrw==}
    +    engines: {node: '>= 10'}
    +    cpu: [x64]
    +    os: [linux]
    +    libc: [glibc]
    +
    +  '@tailwindcss/oxide-linux-x64-musl@4.0.15':
    +    resolution: {integrity: sha512-Gg/Y1XrKEvKpq6WeNt2h8rMIKOBj/W3mNa5NMvkQgMC7iO0+UNLrYmt6zgZufht66HozNpn+tJMbbkZ5a3LczA==}
    +    engines: {node: '>= 10'}
    +    cpu: [x64]
    +    os: [linux]
    +    libc: [musl]
    +
    +  '@tailwindcss/oxide-win32-arm64-msvc@4.0.15':
    +    resolution: {integrity: sha512-7QtSSJwYZ7ZK1phVgcNZpuf7c7gaCj8Wb0xjliligT5qCGCp79OV2n3SJummVZdw4fbTNKUOYMO7m1GinppZyA==}
    +    engines: {node: '>= 10'}
    +    cpu: [arm64]
    +    os: [win32]
    +
    +  '@tailwindcss/oxide-win32-x64-msvc@4.0.15':
    +    resolution: {integrity: sha512-JQ5H+5MLhOjpgNp6KomouE0ZuKmk3hO5h7/ClMNAQ8gZI2zkli3IH8ZqLbd2DVfXDbdxN2xvooIEeIlkIoSCqw==}
    +    engines: {node: '>= 10'}
    +    cpu: [x64]
    +    os: [win32]
    +
    +  '@tailwindcss/oxide@4.0.15':
    +    resolution: {integrity: sha512-e0uHrKfPu7JJGMfjwVNyt5M0u+OP8kUmhACwIRlM+JNBuReDVQ63yAD1NWe5DwJtdaHjugNBil76j+ks3zlk6g==}
    +    engines: {node: '>= 10'}
    +
    +  braces@3.0.3:
    +    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
    +    engines: {node: '>=8'}
    +
    +  detect-libc@1.0.3:
    +    resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
    +    engines: {node: '>=0.10'}
    +    hasBin: true
    +
    +  detect-libc@2.0.3:
    +    resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
    +    engines: {node: '>=8'}
    +
    +  enhanced-resolve@5.18.1:
    +    resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
    +    engines: {node: '>=10.13.0'}
    +
    +  fill-range@7.1.1:
    +    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
    +    engines: {node: '>=8'}
    +
    +  graceful-fs@4.2.11:
    +    resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
    +
    +  is-extglob@2.1.1:
    +    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
    +    engines: {node: '>=0.10.0'}
    +
    +  is-glob@4.0.3:
    +    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
    +    engines: {node: '>=0.10.0'}
    +
    +  is-number@7.0.0:
    +    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
    +    engines: {node: '>=0.12.0'}
    +
    +  jiti@2.4.2:
    +    resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
    +    hasBin: true
    +
    +  lightningcss-darwin-arm64@1.29.2:
    +    resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
    +    engines: {node: '>= 12.0.0'}
    +    cpu: [arm64]
    +    os: [darwin]
    +
    +  lightningcss-darwin-x64@1.29.2:
    +    resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==}
    +    engines: {node: '>= 12.0.0'}
    +    cpu: [x64]
    +    os: [darwin]
    +
    +  lightningcss-freebsd-x64@1.29.2:
    +    resolution: {integrity: sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==}
    +    engines: {node: '>= 12.0.0'}
    +    cpu: [x64]
    +    os: [freebsd]
    +
    +  lightningcss-linux-arm-gnueabihf@1.29.2:
    +    resolution: {integrity: sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==}
    +    engines: {node: '>= 12.0.0'}
    +    cpu: [arm]
    +    os: [linux]
    +
    +  lightningcss-linux-arm64-gnu@1.29.2:
    +    resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==}
    +    engines: {node: '>= 12.0.0'}
    +    cpu: [arm64]
    +    os: [linux]
    +    libc: [glibc]
    +
    +  lightningcss-linux-arm64-musl@1.29.2:
    +    resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==}
    +    engines: {node: '>= 12.0.0'}
    +    cpu: [arm64]
    +    os: [linux]
    +    libc: [musl]
    +
    +  lightningcss-linux-x64-gnu@1.29.2:
    +    resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==}
    +    engines: {node: '>= 12.0.0'}
    +    cpu: [x64]
    +    os: [linux]
    +    libc: [glibc]
    +
    +  lightningcss-linux-x64-musl@1.29.2:
    +    resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==}
    +    engines: {node: '>= 12.0.0'}
    +    cpu: [x64]
    +    os: [linux]
    +    libc: [musl]
    +
    +  lightningcss-win32-arm64-msvc@1.29.2:
    +    resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==}
    +    engines: {node: '>= 12.0.0'}
    +    cpu: [arm64]
    +    os: [win32]
    +
    +  lightningcss-win32-x64-msvc@1.29.2:
    +    resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==}
    +    engines: {node: '>= 12.0.0'}
    +    cpu: [x64]
    +    os: [win32]
    +
    +  lightningcss@1.29.2:
    +    resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==}
    +    engines: {node: '>= 12.0.0'}
    +
    +  micromatch@4.0.8:
    +    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
    +    engines: {node: '>=8.6'}
    +
    +  mri@1.2.0:
    +    resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
    +    engines: {node: '>=4'}
    +
    +  node-addon-api@7.1.1:
    +    resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
    +
    +  picocolors@1.1.1:
    +    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
    +
    +  picomatch@2.3.1:
    +    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
    +    engines: {node: '>=8.6'}
    +
    +  tailwindcss@4.0.15:
    +    resolution: {integrity: sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg==}
    +
    +  tapable@2.2.1:
    +    resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
    +    engines: {node: '>=6'}
    +
    +  to-regex-range@5.0.1:
    +    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
    +    engines: {node: '>=8.0'}
    +
    +snapshots:
    +
    +  '@parcel/watcher-android-arm64@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher-darwin-arm64@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher-darwin-x64@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher-freebsd-x64@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher-linux-arm-glibc@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher-linux-arm-musl@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher-linux-arm64-glibc@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher-linux-arm64-musl@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher-linux-x64-glibc@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher-linux-x64-musl@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher-win32-arm64@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher-win32-ia32@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher-win32-x64@2.5.1':
    +    optional: true
    +
    +  '@parcel/watcher@2.5.1':
    +    dependencies:
    +      detect-libc: 1.0.3
    +      is-glob: 4.0.3
    +      micromatch: 4.0.8
    +      node-addon-api: 7.1.1
    +    optionalDependencies:
    +      '@parcel/watcher-android-arm64': 2.5.1
    +      '@parcel/watcher-darwin-arm64': 2.5.1
    +      '@parcel/watcher-darwin-x64': 2.5.1
    +      '@parcel/watcher-freebsd-x64': 2.5.1
    +      '@parcel/watcher-linux-arm-glibc': 2.5.1
    +      '@parcel/watcher-linux-arm-musl': 2.5.1
    +      '@parcel/watcher-linux-arm64-glibc': 2.5.1
    +      '@parcel/watcher-linux-arm64-musl': 2.5.1
    +      '@parcel/watcher-linux-x64-glibc': 2.5.1
    +      '@parcel/watcher-linux-x64-musl': 2.5.1
    +      '@parcel/watcher-win32-arm64': 2.5.1
    +      '@parcel/watcher-win32-ia32': 2.5.1
    +      '@parcel/watcher-win32-x64': 2.5.1
    +
    +  '@tailwindcss/cli@4.0.15':
    +    dependencies:
    +      '@parcel/watcher': 2.5.1
    +      '@tailwindcss/node': 4.0.15
    +      '@tailwindcss/oxide': 4.0.15
    +      enhanced-resolve: 5.18.1
    +      lightningcss: 1.29.2
    +      mri: 1.2.0
    +      picocolors: 1.1.1
    +      tailwindcss: 4.0.15
    +
    +  '@tailwindcss/node@4.0.15':
    +    dependencies:
    +      enhanced-resolve: 5.18.1
    +      jiti: 2.4.2
    +      tailwindcss: 4.0.15
    +
    +  '@tailwindcss/oxide-android-arm64@4.0.15':
    +    optional: true
    +
    +  '@tailwindcss/oxide-darwin-arm64@4.0.15':
    +    optional: true
    +
    +  '@tailwindcss/oxide-darwin-x64@4.0.15':
    +    optional: true
    +
    +  '@tailwindcss/oxide-freebsd-x64@4.0.15':
    +    optional: true
    +
    +  '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.15':
    +    optional: true
    +
    +  '@tailwindcss/oxide-linux-arm64-gnu@4.0.15':
    +    optional: true
    +
    +  '@tailwindcss/oxide-linux-arm64-musl@4.0.15':
    +    optional: true
    +
    +  '@tailwindcss/oxide-linux-x64-gnu@4.0.15':
    +    optional: true
    +
    +  '@tailwindcss/oxide-linux-x64-musl@4.0.15':
    +    optional: true
    +
    +  '@tailwindcss/oxide-win32-arm64-msvc@4.0.15':
    +    optional: true
    +
    +  '@tailwindcss/oxide-win32-x64-msvc@4.0.15':
    +    optional: true
    +
    +  '@tailwindcss/oxide@4.0.15':
    +    optionalDependencies:
    +      '@tailwindcss/oxide-android-arm64': 4.0.15
    +      '@tailwindcss/oxide-darwin-arm64': 4.0.15
    +      '@tailwindcss/oxide-darwin-x64': 4.0.15
    +      '@tailwindcss/oxide-freebsd-x64': 4.0.15
    +      '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.15
    +      '@tailwindcss/oxide-linux-arm64-gnu': 4.0.15
    +      '@tailwindcss/oxide-linux-arm64-musl': 4.0.15
    +      '@tailwindcss/oxide-linux-x64-gnu': 4.0.15
    +      '@tailwindcss/oxide-linux-x64-musl': 4.0.15
    +      '@tailwindcss/oxide-win32-arm64-msvc': 4.0.15
    +      '@tailwindcss/oxide-win32-x64-msvc': 4.0.15
    +
    +  braces@3.0.3:
    +    dependencies:
    +      fill-range: 7.1.1
    +
    +  detect-libc@1.0.3: {}
    +
    +  detect-libc@2.0.3: {}
    +
    +  enhanced-resolve@5.18.1:
    +    dependencies:
    +      graceful-fs: 4.2.11
    +      tapable: 2.2.1
    +
    +  fill-range@7.1.1:
    +    dependencies:
    +      to-regex-range: 5.0.1
    +
    +  graceful-fs@4.2.11: {}
    +
    +  is-extglob@2.1.1: {}
    +
    +  is-glob@4.0.3:
    +    dependencies:
    +      is-extglob: 2.1.1
    +
    +  is-number@7.0.0: {}
    +
    +  jiti@2.4.2: {}
    +
    +  lightningcss-darwin-arm64@1.29.2:
    +    optional: true
    +
    +  lightningcss-darwin-x64@1.29.2:
    +    optional: true
    +
    +  lightningcss-freebsd-x64@1.29.2:
    +    optional: true
    +
    +  lightningcss-linux-arm-gnueabihf@1.29.2:
    +    optional: true
    +
    +  lightningcss-linux-arm64-gnu@1.29.2:
    +    optional: true
    +
    +  lightningcss-linux-arm64-musl@1.29.2:
    +    optional: true
    +
    +  lightningcss-linux-x64-gnu@1.29.2:
    +    optional: true
    +
    +  lightningcss-linux-x64-musl@1.29.2:
    +    optional: true
    +
    +  lightningcss-win32-arm64-msvc@1.29.2:
    +    optional: true
    +
    +  lightningcss-win32-x64-msvc@1.29.2:
    +    optional: true
    +
    +  lightningcss@1.29.2:
    +    dependencies:
    +      detect-libc: 2.0.3
    +    optionalDependencies:
    +      lightningcss-darwin-arm64: 1.29.2
    +      lightningcss-darwin-x64: 1.29.2
    +      lightningcss-freebsd-x64: 1.29.2
    +      lightningcss-linux-arm-gnueabihf: 1.29.2
    +      lightningcss-linux-arm64-gnu: 1.29.2
    +      lightningcss-linux-arm64-musl: 1.29.2
    +      lightningcss-linux-x64-gnu: 1.29.2
    +      lightningcss-linux-x64-musl: 1.29.2
    +      lightningcss-win32-arm64-msvc: 1.29.2
    +      lightningcss-win32-x64-msvc: 1.29.2
    +
    +  micromatch@4.0.8:
    +    dependencies:
    +      braces: 3.0.3
    +      picomatch: 2.3.1
    +
    +  mri@1.2.0: {}
    +
    +  node-addon-api@7.1.1: {}
    +
    +  picocolors@1.1.1: {}
    +
    +  picomatch@2.3.1: {}
    +
    +  tailwindcss@4.0.15: {}
    +
    +  tapable@2.2.1: {}
    +
    +  to-regex-range@5.0.1:
    +    dependencies:
    +      is-number: 7.0.0
    diff --git a/src/YaeBlog/tailwind.ps1 b/src/YaeBlog/tailwind.ps1
    new file mode 100644
    index 0000000..3d19da1
    --- /dev/null
    +++ b/src/YaeBlog/tailwind.ps1
    @@ -0,0 +1,11 @@
    +#!/pwsh
    +
    +[cmdletbinding()]
    +param(
    +    [string]$Output = "wwwroot"
    +)
    +
    +end {
    +    Write-Host "Build tailwind css into $Output."
    +    pnpm tailwindcss -i wwwroot/tailwind.css -o $Output/tailwind.g.css
    +}
    diff --git a/YaeBlog/wwwroot/fonts/.gitattributes b/src/YaeBlog/wwwroot/fonts/.gitattributes
    similarity index 100%
    rename from YaeBlog/wwwroot/fonts/.gitattributes
    rename to src/YaeBlog/wwwroot/fonts/.gitattributes
    diff --git a/src/YaeBlog/wwwroot/fonts/fa-brands-400.woff2 b/src/YaeBlog/wwwroot/fonts/fa-brands-400.woff2
    new file mode 100644
    index 0000000..6af4c60
    --- /dev/null
    +++ b/src/YaeBlog/wwwroot/fonts/fa-brands-400.woff2
    @@ -0,0 +1,3 @@
    +version https://git-lfs.github.com/spec/v1
    +oid sha256:061dd5c333459ea42dba764617793fd6ea2d316b7ab644f157e4d2354dac02af
    +size 101224
    diff --git a/src/YaeBlog/wwwroot/fonts/fa-regular-400.woff2 b/src/YaeBlog/wwwroot/fonts/fa-regular-400.woff2
    new file mode 100644
    index 0000000..296655c
    --- /dev/null
    +++ b/src/YaeBlog/wwwroot/fonts/fa-regular-400.woff2
    @@ -0,0 +1,3 @@
    +version https://git-lfs.github.com/spec/v1
    +oid sha256:81159a6b36876a5545555ae689144f074e2fc802d57d36f2c21bc6f3a12f4e48
    +size 18988
    diff --git a/src/YaeBlog/wwwroot/fonts/fa-solid-900.woff2 b/src/YaeBlog/wwwroot/fonts/fa-solid-900.woff2
    new file mode 100644
    index 0000000..9b80394
    --- /dev/null
    +++ b/src/YaeBlog/wwwroot/fonts/fa-solid-900.woff2
    @@ -0,0 +1,3 @@
    +version https://git-lfs.github.com/spec/v1
    +oid sha256:bdd7887ef769948024a5cc37a018f19da6a9b355b4a09973836115e0d31ead55
    +size 113152
    diff --git a/src/YaeBlog/wwwroot/images/alipay-code.jpeg b/src/YaeBlog/wwwroot/images/alipay-code.jpeg
    new file mode 100644
    index 0000000..480598c
    --- /dev/null
    +++ b/src/YaeBlog/wwwroot/images/alipay-code.jpeg
    @@ -0,0 +1,3 @@
    +version https://git-lfs.github.com/spec/v1
    +oid sha256:9dfcb06c706d121c2ea672407843e0ad011e43531ef2ea4f2141fca2795012da
    +size 102287
    diff --git a/YaeBlog/wwwroot/images/avatar.png b/src/YaeBlog/wwwroot/images/avatar.png
    similarity index 100%
    rename from YaeBlog/wwwroot/images/avatar.png
    rename to src/YaeBlog/wwwroot/images/avatar.png
    diff --git a/YaeBlog/wwwroot/images/favicon.ico b/src/YaeBlog/wwwroot/images/favicon.ico
    similarity index 100%
    rename from YaeBlog/wwwroot/images/favicon.ico
    rename to src/YaeBlog/wwwroot/images/favicon.ico
    diff --git a/src/YaeBlog/wwwroot/images/wechat-code.jpeg b/src/YaeBlog/wwwroot/images/wechat-code.jpeg
    new file mode 100644
    index 0000000..a036e04
    --- /dev/null
    +++ b/src/YaeBlog/wwwroot/images/wechat-code.jpeg
    @@ -0,0 +1,3 @@
    +version https://git-lfs.github.com/spec/v1
    +oid sha256:527ba104ee72f1f7b934f7a7f8ed8cb2edf905ba39d1c0f89d986c5421871f3e
    +size 49545
    diff --git a/src/YaeBlog/wwwroot/images/xiaohongshu-seeklogo.svg b/src/YaeBlog/wwwroot/images/xiaohongshu-seeklogo.svg
    new file mode 100644
    index 0000000..3a2fb33
    --- /dev/null
    +++ b/src/YaeBlog/wwwroot/images/xiaohongshu-seeklogo.svg
    @@ -0,0 +1,44 @@
    +
    +
    +
    +
    +  
    +  
    +  
    +    
    +  
    +
    diff --git a/src/YaeBlog/wwwroot/tailwind.css b/src/YaeBlog/wwwroot/tailwind.css
    new file mode 100644
    index 0000000..f1d8c73
    --- /dev/null
    +++ b/src/YaeBlog/wwwroot/tailwind.css
    @@ -0,0 +1 @@
    +@import "tailwindcss";
    diff --git a/third-party/BlazorSvgComponents b/third-party/BlazorSvgComponents
    new file mode 160000
    index 0000000..909448d
    --- /dev/null
    +++ b/third-party/BlazorSvgComponents
    @@ -0,0 +1 @@
    +Subproject commit 909448d9f5ad274b6e0b61355381a45e63bbc735