Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/workflows/packer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ jobs:

- name: Run Packer for ${{ matrix.version }}
# 分发包中应当包含全部内容
run: ./Packer --version="${{ matrix.version }}"
run: ./Packer --version="${{ matrix.version }}" --grouped --flattened
# 运行逻辑:内容有更改 或 手动运行
if: steps.check-changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch'

Expand All @@ -122,6 +122,15 @@ jobs:
Minecraft-Mod-Language-Modpack-${{ matrix.version }}.zip
${{ matrix.version }}.md5
if: steps.check-changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch'

# feat:按命名空间打小包
- name: Upload Artifact for ${{ matrix.version }} grouped
uses: actions/upload-artifact@v4
with:
name: Minecraft-Mod-Language-Modpack-${{ matrix.version }}-grouped
path: |
Minecraft-Mod-Language-Modpack-${{ matrix.version }}-namespaced.zip
if: steps.check-changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch'

upload:
concurrency:
Expand Down Expand Up @@ -154,6 +163,8 @@ jobs:
uses: actions/download-artifact@v4
with:
path: artifacts/

# Pending: 把按命名空间传的包也传上去

# feat: UTC 20:00~21:00 取消上传(避开远程服务器的4:00-4:10)
- name: Fail at inappropriate time
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-packer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ jobs:

- name: Run Packer for ${{ matrix.version }}
# 部分包原则:Packer和配置均没有改动
run: ./Packer --version="${{ matrix.version }}" --increment=${{ steps.check-critical-changes.outputs.changed == 'false' }}
run: ./Packer --version="${{ matrix.version }}" --increment=${{ steps.check-critical-changes.outputs.changed == 'false' }} --flattened
# 运行逻辑:内容有更改 或 手动运行
if: steps.check-changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch'

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# CFPA-specifics
Minecraft-Mod-Language-Package-*.zip
Minecraft-Mod-Language-Modpack-*.zip
*.md5
build/
Packer.exe
Expand Down
4 changes: 4 additions & 0 deletions Packer-Doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
暂时闲置,以待后续需求。
- string
排除的命名空间。<!--以[S部分](./S.-本仓库的结构向导)中定义的 **[namespace]** | **(命名空间)**为准。-->
- `fallbackVersions` list
该配置所允许的回退版本。对每个模组文件夹,打包器会先寻找 `version` 字段指向的版本,再在这里按顺序寻找回退版本;打包器会选择找到的**第一个**版本,而不会加载其他版本的内容。
- string
回退版本,以 `projects/` 中的文件夹名称为准。
- `floating` object
打包流程中的*可变配置*,可能被文件结构中的**局域配置文件**改写。包含的内容都是**低于**命名空间层级的,因为局域配置文件就是放在命名空间一级中的。
- `inclusionDomains` list
Expand Down
3 changes: 2 additions & 1 deletion config/packer/1.12.2.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
],
"exclusionNamespaces": [
"srparasites"
]
],
"fallbackVersions": []
},
"floating": {
"inclusionDomains": [
Expand Down
3 changes: 2 additions & 1 deletion config/packer/1.16-fabric.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"1.16.5 Fabric"
],
"exclusionMods": [],
"exclusionNamespaces": []
"exclusionNamespaces": [],
"fallbackVersions": []
},
"floating": {
"inclusionDomains": [
Expand Down
3 changes: 2 additions & 1 deletion config/packer/1.16.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"1.16.5 Forge"
],
"exclusionMods": [],
"exclusionNamespaces": []
"exclusionNamespaces": [],
"fallbackVersions": []
},
"floating": {
"inclusionDomains": [
Expand Down
3 changes: 2 additions & 1 deletion config/packer/1.18-fabric.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"exclusionMods": [],
"exclusionNamespaces": [
"litematica"
]
],
"fallbackVersions": []
},
"floating": {
"inclusionDomains": [
Expand Down
3 changes: 2 additions & 1 deletion config/packer/1.18.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"thermal",
"tinkers_things",
"createaddition"
]
],
"fallbackVersions": []
},
"floating": {
"inclusionDomains": [
Expand Down
3 changes: 3 additions & 0 deletions config/packer/1.19.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
"exclusionNamespaces": [
"nochatreports",
"illager_additions"
],
"fallbackVersions": [
"1.18"
]
},
"floating": {
Expand Down
4 changes: 4 additions & 0 deletions config/packer/1.20-fabric.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
"exclusionMods": [],
"exclusionNamespaces": [
"hexcasting"
],
"fallbackVersions": [
"1.19-fabric",
"1.18-fabric"
]
},
"floating": {
Expand Down
4 changes: 4 additions & 0 deletions config/packer/1.20.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
"biomancy",
"create-sound-of-steam",
"occultism"
],
"fallbackVersions": [
"1.19",
"1.18"
]
},
"floating": {
Expand Down
7 changes: 6 additions & 1 deletion config/packer/1.21-fabric.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
"1.21 Fabric"
],
"exclusionMods": [],
"exclusionNamespaces": []
"exclusionNamespaces": [],
"fallbackVersions": [
"1.20-fabric",
"1.19",
"1.18-fabric"
]
},
"floating": {
"inclusionDomains": [
Expand Down
5 changes: 5 additions & 0 deletions config/packer/1.21.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
"exclusionNamespaces": [
"create-sound-of-steam",
"replication"
],
"fallbackVersions": [
"1.20",
"1.19",
"1.18"
]
},
"floating": {
Expand Down
7 changes: 6 additions & 1 deletion config/packer/26.1-fabric.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
"26.1 Fabric"
],
"exclusionMods": [],
"exclusionNamespaces": []
"exclusionNamespaces": [],
"fallbackVersions": [
"1.21-fabric",
"1.20-fabric",
"1.19"
]
},
"floating": {
"inclusionDomains": [
Expand Down
7 changes: 6 additions & 1 deletion config/packer/26.1.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
"26.1"
],
"exclusionMods": [],
"exclusionNamespaces": []
"exclusionNamespaces": [],
"fallbackVersions": [
"1.21",
"1.20",
"1.19"
]
},
"floating": {
"inclusionDomains": [
Expand Down
62 changes: 54 additions & 8 deletions src/Packer/Extensions/ArchiveExtension.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
using System;
using Packer.Models;
using Serilog;
using SharpCompress.Writers;
using SharpCompress.Writers.Tar;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Packer.Extensions
{
Expand All @@ -8,18 +17,55 @@ namespace Packer.Extensions
/// </summary>
static public class ArchiveExtension
{
/// <summary>
/// 校验将要传入压缩包的的文件是否存在重名<br />
/// </summary>
/// <param name="archive">所查询的压缩包</param>
/// <param name="entryName">所查询的路径</param>
/// <exception cref="InvalidOperationException">传入文件存在重名。</exception>
public static void ValidateEntryDistinctness(this ZipArchive archive, string entryName)

public static ZipArchiveEntry CreateEntryValidated(this ZipArchive archive, string entryName)
{
var normalized = entryName.NormalizePath();
if (archive.GetEntry(entryName) != null)
{
throw new InvalidOperationException($"An entry named {entryName} already exists.");
}
Log.Debug("写入路径 {0}", normalized);
return archive.CreateEntry(normalized);
}

public static async Task WriteDirect(this ZipArchive archive, IEnumerable<IResourceFileProvider> providers)
{
foreach (var provider in providers)
{
var entry = archive.CreateEntryValidated(provider.Destination);
using var stream = entry.Open();
await provider.WriteToStream(stream);
}
}

public static async Task WriteGrouped(this ZipArchive archive, IEnumerable<IResourceFileProvider> providers)
{
var grouped =
from provider in providers
group provider by provider.Destination.GetNamespace();
foreach (var group in grouped)
{
var targetPath = $"assets/{group.Key}.tar.lzma";
var entry = archive.CreateEntryValidated(targetPath);
using var stream = await entry.OpenAsync();
using (var entryWriter = new TarWriter(stream, new TarWriterOptions(SharpCompress.Common.CompressionType.LZip)))
{
foreach (var provider in group)
{
using var dataStream = new MemoryStream();
await provider.WriteToStream(dataStream);
dataStream.Seek(0, SeekOrigin.Begin);
await entryWriter.WriteAsync(provider.Destination.Split('/', 3)[2], dataStream);
}
}

var md5 = stream.ComputeMD5();
var md5Entry = archive.CreateEntryValidated($"assets/{group.Key}.md5");
using var md5Writer = new StreamWriter(md5Entry.Open(),
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
md5Writer.Write(md5);
}
}
}
}
8 changes: 7 additions & 1 deletion src/Packer/Extensions/ContentExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ public static partial class ContentExtension
public static string NormalizePath(this string path)
=> path.Replace('\\', '/'); // 修正正反斜杠导致的压缩文件读取问题

public static string GetNamespace(this string path)
{
// assets/<namespace>/...
return path.Split('/')[1];
}


[GeneratedRegex(@"^[a-z0-9_.-]+$", RegexOptions.Singleline)]
internal static partial Regex ValidNamespaceRegex();
Expand Down Expand Up @@ -107,7 +113,7 @@ public static string LogToDebug(this string message)
/// <returns></returns>
public static string ComputeMD5(this Stream stream)
{
stream.Seek(0, SeekOrigin.Begin); // 确保文件流的位置被重置
stream.Seek(0, SeekOrigin.Begin);
return Convert.ToHexString(MD5.Create().ComputeHash(stream));
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/Packer/Helpers/ConfigHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,18 @@ public static class ConfigHelpers
/// </summary>
/// <param name="configTemplate">配置路径模板</param>
/// <param name="version">打包版本,用于定位全局配置</param>
public static async Task<Config> RetrieveConfig(string configTemplate, string version)
public static Config RetrieveConfig(string configTemplate, string version)
{
Log.Information("正在获取配置。目标版本:{0}", version);

var configPath = string.Format(configTemplate, version);

Log.Information("配置位置:{0}", configPath);

var content = await File.ReadAllBytesAsync(configPath);
using var stream = File.OpenRead(configPath);

return JsonSerializer.Deserialize<Config>(
content,
stream,
new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase })!;
}

Expand Down
75 changes: 75 additions & 0 deletions src/Packer/Helpers/EnumerationHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Packer.Extensions;
using Packer.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Packer.Helpers
{
internal static class EnumerationHelper
{
public static IEnumerable<IResourceFileProvider> EnumerateUnmerged(IEnumerable<string> targetModIdentifiers,
Config config,
IEnumerable<string> acceptableVersions)
{
return
// ./projects/assets/<projectSlug>...
from modDirectory in new DirectoryInfo("./projects/assets").EnumerateDirectories()
let modIdentifier = modDirectory.Name
where targetModIdentifiers.Count() == 0 // 未提供列表,全部打包
|| targetModIdentifiers.Contains(modIdentifier) // 有列表,仅打包列表中的项
where !config.Base.ExclusionMods.Contains(modIdentifier) // 没有被明确排除
// .../<version>
// 在此只选择可选版本中最新的一个,其他的不参与打包
let versionedDirectory = acceptableVersions.Select(version => modDirectory.GetDirectories(version).FirstOrDefault())
.FirstOrDefault(_ => _ is not null)
where versionedDirectory is not null
// .../<namespace>-CFPA-<author>
from namespaceDirectory in versionedDirectory.EnumerateDirectories()
let namespaceName = namespaceDirectory.Name
where !config.Base.ExclusionNamespaces.Contains(namespaceName) // 没有被明确排除
where namespaceName.ValidateNamespace() // 不是非法名称
// .../*
from provider in namespaceDirectory.EnumerateProviders(config)
select provider;
}

public static IEnumerable<IResourceFileProvider> PostProcess(this IEnumerable<IResourceFileProvider> providers, Config config)
{
return
from provider in providers
select config.Floating.CharacterReplacement // 内容的字符替换
.Aggregate(seed: provider,
(accumulate, replacement)
=> accumulate.ReplaceContent(
replacement.Key,
replacement.Value)) into provider
select config.Floating.DestinationReplacement // 全局路径替换:预留
.Aggregate(seed: provider,
(accumulate, replacement)
=> accumulate.ReplaceDestination(
replacement.Key,
replacement.Value));
}

public static IEnumerable<IResourceFileProvider> MergeDeep(this IEnumerable<IResourceFileProvider> providers)
{
return
from provider in providers
group provider by provider.Destination into destinationGroup
select destinationGroup
.Aggregate(seed: null as IResourceFileProvider, // 合并文件
(accumulate, next)
=> next.ApplyTo(
accumulate));
}

public static IEnumerable<IResourceFileProvider> MergeShallow(this IEnumerable<IResourceFileProvider> providers)
{
return from provider in providers
group provider by provider.Destination into destinationGroup
select destinationGroup.First();
}
}
}
Loading
Loading