Skip to content

Commit c4b5cb4

Browse files
committed
Fix potential issues with Paths
Attempt to address "No executables were found in container when trying to infer the main executable."
1 parent 72b3b03 commit c4b5cb4

File tree

2 files changed

+162
-1
lines changed

2 files changed

+162
-1
lines changed

src/DotnetPackaging.Exe/ExePackagingService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ private Maybe<string> InferExecutableName(IContainer contextDir, Maybe<string> p
264264
{
265265
var candidates = contextDir
266266
.ResourcesWithPathsRecursive()
267-
.Where(r => r.Path.Value.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
267+
.Where(resource => resource.FullPath().Extension().Match(ext => string.Equals(ext, "exe", StringComparison.OrdinalIgnoreCase), () => false))
268268
.Select(path => new
269269
{
270270
Relative = path.FullPath(),
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Reflection;
4+
using System.Text;
5+
using CSharpFunctionalExtensions;
6+
using DotnetPackaging.Exe;
7+
using FluentAssertions;
8+
using Zafiro.DivineBytes;
9+
using Xunit;
10+
using DivinePath = Zafiro.DivineBytes.Path;
11+
12+
namespace DotnetPackaging.Exe.Tests;
13+
14+
public class ExePackagingServiceTests
15+
{
16+
private static readonly MethodInfo InferExecutableNameMethod =
17+
typeof(ExePackagingService).GetMethod("InferExecutableName", BindingFlags.Instance | BindingFlags.NonPublic)
18+
?? throw new InvalidOperationException("Unable to locate InferExecutableName on ExePackagingService.");
19+
20+
[Fact]
21+
public void Infers_executable_matching_project_name_from_publish_output()
22+
{
23+
var publishOutput = DotnetPublishOutputDouble.Simulate(
24+
"TestApp.Desktop.exe",
25+
"TestApp.Desktop.dll",
26+
"TestApp.Desktop.runtimeconfig.json",
27+
"runtimes/win-x64/native/createdump.exe",
28+
"tools/helper.exe");
29+
30+
var service = new ExePackagingService();
31+
var inferred = InvokeInferExecutableName(service, publishOutput, Maybe<string>.From("TestApp.Desktop"));
32+
33+
inferred.HasValue.Should().BeTrue();
34+
inferred.Value.Should().Be("TestApp.Desktop.exe");
35+
}
36+
37+
[Fact]
38+
public void Prefers_single_candidate_when_project_name_is_missing()
39+
{
40+
var publishOutput = DotnetPublishOutputDouble.Simulate(
41+
"win-x64/publish/Application.exe",
42+
"win-x64/publish/Application.dll");
43+
44+
var service = new ExePackagingService();
45+
var inferred = InvokeInferExecutableName(service, publishOutput, Maybe<string>.None);
46+
47+
var expected = new DivinePath("win-x64/publish/Application.exe")
48+
.MakeRelativeTo(DivinePath.Empty)
49+
.ToString();
50+
51+
inferred.HasValue.Should().BeTrue();
52+
inferred.Value.Should().Be(expected);
53+
}
54+
55+
[Fact]
56+
public void Chooses_shallowest_candidate_when_multiple_remain()
57+
{
58+
var publishOutput = DotnetPublishOutputDouble.Simulate(
59+
"bin/Release/net8.0/win-x64/App.exe",
60+
"tools/cli/Helper.exe",
61+
"Helper.exe");
62+
63+
var service = new ExePackagingService();
64+
var inferred = InvokeInferExecutableName(service, publishOutput, Maybe<string>.None);
65+
66+
inferred.HasValue.Should().BeTrue();
67+
inferred.Value.Should().Be("Helper.exe");
68+
}
69+
70+
[Fact]
71+
public void Returns_none_when_only_createdump_is_present()
72+
{
73+
var publishOutput = DotnetPublishOutputDouble.Simulate(
74+
"createdump.exe",
75+
"runtimes/win-x64/native/createdump.exe");
76+
77+
var service = new ExePackagingService();
78+
var inferred = InvokeInferExecutableName(service, publishOutput, Maybe<string>.None);
79+
80+
inferred.HasValue.Should().BeFalse();
81+
}
82+
83+
private static Maybe<string> InvokeInferExecutableName(ExePackagingService service, IContainer container, Maybe<string> projectName)
84+
{
85+
var result = InferExecutableNameMethod.Invoke(service, new object[] { container, projectName });
86+
return (Maybe<string>)result!;
87+
}
88+
89+
private abstract class ContainerNode : IContainer
90+
{
91+
protected List<INamedContainer> SubcontainerList { get; } = new();
92+
protected List<INamedByteSource> ResourceList { get; } = new();
93+
94+
public IEnumerable<INamedContainer> Subcontainers => SubcontainerList;
95+
public IEnumerable<INamedByteSource> Resources => ResourceList;
96+
97+
protected ContainerNode EnsureDirectory(IEnumerable<string> fragments)
98+
{
99+
var current = this;
100+
foreach (var fragment in fragments)
101+
{
102+
var match = current.SubcontainerList
103+
.OfType<DotnetPublishDirectoryDouble>()
104+
.FirstOrDefault(d => string.Equals(d.Name, fragment, StringComparison.OrdinalIgnoreCase));
105+
106+
if (match is null)
107+
{
108+
match = new DotnetPublishDirectoryDouble(fragment);
109+
current.SubcontainerList.Add(match);
110+
}
111+
112+
current = match;
113+
}
114+
115+
return current;
116+
}
117+
118+
internal void AddResource(INamedByteSource resource)
119+
{
120+
ResourceList.Add(resource);
121+
}
122+
}
123+
124+
private sealed class DotnetPublishDirectoryDouble : ContainerNode, INamedContainer
125+
{
126+
public DotnetPublishDirectoryDouble(string name)
127+
{
128+
Name = name;
129+
}
130+
131+
public string Name { get; }
132+
}
133+
134+
private sealed class DotnetPublishOutputDouble : ContainerNode
135+
{
136+
public static IContainer Simulate(params string[] relativeFilePaths)
137+
{
138+
var root = new DotnetPublishOutputDouble();
139+
foreach (var relative in relativeFilePaths)
140+
{
141+
root.Add(new DivinePath(relative.Replace("\\", "/")));
142+
}
143+
144+
return root;
145+
}
146+
147+
private void Add(DivinePath filePath)
148+
{
149+
if (!filePath.RouteFragments.Any())
150+
{
151+
return;
152+
}
153+
154+
var parent = EnsureDirectory(filePath.RouteFragments.SkipLast(1));
155+
var extension = filePath.Extension().GetValueOrDefault("noext");
156+
var relative = filePath.MakeRelativeTo(DivinePath.Empty).ToString();
157+
var payload = Encoding.UTF8.GetBytes($"{extension}:{relative}");
158+
parent.AddResource(new Resource(filePath.Name(), ByteSource.FromBytes(payload)));
159+
}
160+
}
161+
}

0 commit comments

Comments
 (0)