Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[msbuild] Add ILStrip'ing for net6 applications. Fixes #11445. #12563

Merged
merged 29 commits into from
Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b4bad89
[msbuild] Add ILStrip'ing for net6 applications
chamons Aug 24, 2021
87c5c96
Apply suggestions from code review
chamons Aug 26, 2021
fb4fef5
Rework EnableAssemblyILStripping conditions
chamons Aug 26, 2021
7b355ed
Merge branch 'main' into il_strip_net6
chamons Aug 26, 2021
0f13eb3
Remove questionable set
chamons Aug 26, 2021
cf78956
Merge branch 'il_strip_net6' of github.com:chamons/xamarin-macios int…
chamons Aug 26, 2021
aea669e
Code review comments
chamons Aug 26, 2021
cce0c87
Update tests/dotnet/UnitTests/PostBuildTest.cs
chamons Aug 27, 2021
80bc6f1
Apply suggestions from code review
chamons Aug 27, 2021
1c30a7f
Extend BuildIpaTest to include strip testing
chamons Aug 27, 2021
8abf282
Fix makedir missing windows tags
chamons Aug 27, 2021
40b0985
Implement XVS redirect
chamons Aug 30, 2021
c96c815
Fix build
chamons Aug 30, 2021
2a07c73
Merge branch 'main' into il_strip_net6
chamons Sep 17, 2021
2d922d5
Make AssertBundleAssembliesStripStatus recursive
chamons Sep 17, 2021
0ac7fbf
Add ILStrip to merge targets and bump to RC2 with fix
chamons Sep 20, 2021
bca5bc3
Merge branch 'main' into il_strip_net6
chamons Sep 20, 2021
de175fd
Add some comments
chamons Sep 20, 2021
7be9298
Add sessionid that started all of this
chamons Sep 20, 2021
1e02818
Actually use correct ILStrip and fix XVS arg
chamons Sep 21, 2021
ffa97b9
Use full name of ILStrip to work around other being loaded first
chamons Sep 21, 2021
1922f7d
Merge branch 'main' into il_strip_net6
rolfbjarne Sep 23, 2021
7d5cdda
Set ShouldCopyToBuildServer on ITaskCallback
chamons Sep 27, 2021
53a7a38
Merge branch 'il_strip_net6' of github.com:chamons/xamarin-macios int…
chamons Sep 27, 2021
7a9e22e
Merge branch 'main' into il_strip_net6
chamons Sep 27, 2021
a027e74
Fix unit test after main merge
chamons Sep 27, 2021
f215ae8
Merge branch 'main' into il_strip_net6
chamons Sep 28, 2021
ca0cab7
Merge branch 'main' into il_strip_net6
rolfbjarne Sep 30, 2021
f5c2845
Merge branch 'main' into il_strip_net6
chamons Oct 4, 2021
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
38 changes: 38 additions & 0 deletions dotnet/targets/Xamarin.Shared.Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<UsingTask TaskName="Xamarin.MacDev.Tasks.LinkNativeCode" AssemblyFile="$(_XamarinTaskAssembly)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.SymbolStrip" AssemblyFile="$(_XamarinTaskAssembly)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.MergeAppBundles" AssemblyFile="$(_XamarinTaskAssembly)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.ILStrip" AssemblyFile="$(_XamarinTaskAssembly)" />

<!-- Project types and how do we distinguish between them

Expand Down Expand Up @@ -209,6 +210,7 @@
<!-- We re-use ComputeFilesToPublish & CopyFilesToPublishDirectory to copy files to the .app -->
<!-- ComputeFilesToPublish will run ILLink -->
<!-- single-rid build (either plain single, or inner build for multi-rid build) -->
<!-- Note - _ComputeStripAssemblyIL must be before _StripAssemblyIL as msbuild DependsOn do not execute before Conditions are evaluated -->
<CreateAppBundleDependsOn Condition="'$(RuntimeIdentifiers)' == ''">
$(CreateAppBundleDependsOn);
_CopyResourcesToBundle;
Expand All @@ -226,6 +228,8 @@
_ComputeFrameworkFilesToPublish;
_ComputeDynamicLibrariesToPublish;
ComputeFilesToPublish;
_ComputeStripAssemblyIL;
_StripAssemblyIL;
_LoadLinkerOutput;
_CompileNativeExecutable;
_LinkNativeExecutable;
Expand Down Expand Up @@ -628,6 +632,40 @@
</ItemGroup>
</Target>

<Target Name="_ComputeStripAssemblyIL" Condition=" '$(EnableAssemblyILStripping)' == '' " DependsOnTargets="_ComputeVariables;ComputeFilesToPublish">
<PropertyGroup>
<!-- Don't strip IL by default -->
<EnableAssemblyILStripping>false</EnableAssemblyILStripping>

<!-- Strip if we are AOT and Release -->
<EnableAssemblyILStripping Condition="'$(_RunAotCompiler)' == 'true' And '$(Configuration)' == 'Release'">true</EnableAssemblyILStripping>

<!-- Don't strip if we are running the interpreter -->
<EnableAssemblyILStripping Condition="'$(MtouchInterpreter)' != ''">false</EnableAssemblyILStripping>
</PropertyGroup>
</Target>

<!-- The DependsOnTargets here will not force EnableAssemblyILStripping to be calculated before the condition is evaulated. The order in CreateAppBundleDependsOn matters. -->
<Target Name="_StripAssemblyIL" Condition="'$(EnableAssemblyILStripping)' == 'true'" DependsOnTargets="_ComputeStripAssemblyIL">
rolfbjarne marked this conversation as resolved.
Show resolved Hide resolved
<PropertyGroup>
<_StrippedAssemblyDirectory>$(DeviceSpecificIntermediateOutputPath)\stripped</_StrippedAssemblyDirectory>
</PropertyGroup>
<MakeDir SessionId="$(BuildSessionId)" Condition="'$(IsMacEnabled)' == 'true'" Directories="$(_StrippedAssemblyDirectory)" />
<ItemGroup>
<_AssembliesToBeStripped Include="@(ResolvedFileToPublish)" Condition="'%(Extension)' == '.dll'">
<OutputPath>$(_StrippedAssemblyDirectory)\%(Filename)%(Extension)</OutputPath>
</_AssembliesToBeStripped>
</ItemGroup>
<Xamarin.MacDev.Tasks.ILStrip Assemblies="@(_AssembliesToBeStripped)" SessionId="$(BuildSessionId)">
<Output TaskParameter="StrippedAssemblies" PropertyName="_StrippedAssemblies" />
</Xamarin.MacDev.Tasks.ILStrip>
<ItemGroup>
<ResolvedFileToPublish Remove="@(_AssembliesToBeStripped)" />
<ResolvedFileToPublish Include="@(_StrippedAssemblies)" />
</ItemGroup>
</Target>


<Target Name="_LoadLinkerOutput" DependsOnTargets="ComputeFilesToPublish">
<!-- Load _MainFile -->
<ReadItemsFromFile SessionId="$(BuildSessionId)" File="$(_LinkerItemsDirectory)/_MainFile.items" Condition="Exists('$(_LinkerItemsDirectory)/_MainFile.items')">
Expand Down
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
<MicrosoftDotnetSdkInternalPackageVersion>6.0.100-rtm.21480.21</MicrosoftDotnetSdkInternalPackageVersion>
<MicrosoftNETILLinkTasksPackageVersion>6.0.100-1.21473.1</MicrosoftNETILLinkTasksPackageVersion>
<MicrosoftDotNetBuildTasksFeedPackageVersion>6.0.0-beta.21212.6</MicrosoftDotNetBuildTasksFeedPackageVersion>
<MicrosoftNETILStripTasksPackageVersion>6.0.0-rc.2.21468.3</MicrosoftNETILStripTasksPackageVersion>
</PropertyGroup>
</Project>
1 change: 1 addition & 0 deletions msbuild/ILMerge.targets
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<MergedAssemblies Include="@(ReferencePath)" Condition="'%(FileName)' == 'Xamarin.MacDev'" />
<MergedAssemblies Include="@(ReferencePath)" Condition="'%(FileName)' == 'DotNetZip'" />
<MergedAssemblies Include="@(ReferencePath)" Condition="'%(FileName)' == 'ILLink.Tasks'" />
<MergedAssemblies Include="@(ReferencePath)" Condition="'%(FileName)' == 'ILStrip'" />
<MergedAssemblies Include="@(ReferencePath)" Condition="'%(FileName)' == 'Newtonsoft.Json'" />
<MergedAssemblies Include="@(ReferencePath)" Condition="'%(FileName)' == 'Renci.SshNet'" />
<MergedAssemblies Include="@(ReferencePath)" Condition="'%(FileName)' == 'SshNet.Security.Cryptography'" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public abstract class ParseBundlerArgumentsTaskBase : XamarinTask {
public string Registrar { get; set; }

// This is input too
[Output]
public string NoStrip { get; set; }

[Output]
public int Verbosity { get; set; }

Expand Down Expand Up @@ -142,6 +145,10 @@ public override bool Execute ()
value = hasValue ? value : nextValue; // requires a value, which might be the next option
xml.Add (value);
break;
case "nostrip":
// Output is EnableAssemblyILStripping so we enable if --nostrip=false and disable if true
NoStrip = ParseBool (value) ? "false" : "true";
break;
default:
Log.LogMessage (MessageImportance.Low, "Skipping unknown argument '{0}' with value '{1}'", name, value);
break;
Expand Down
1 change: 1 addition & 0 deletions msbuild/Xamarin.Shared/Xamarin.Shared.targets
Original file line number Diff line number Diff line change
Expand Up @@ -1647,6 +1647,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<Output TaskParameter="Registrar" PropertyName="_BundlerRegistrar" />
<Output TaskParameter="Verbosity" PropertyName="_BundlerVerbosity" />
<Output TaskParameter="XmlDefinitions" ItemName="_BundlerXmlDefinitions" />
<Output TaskParameter="NoStrip" PropertyName="EnableAssemblyILStripping" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This task should take EnableAssemblyILStripping as input as well, otherwise it won't be possible for users to override the default by setting EnableAssemblyILStripping to something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is correct.

I hacked up a test project: https://gist.github.com/chamons/abe82c128d909df53a1780c792394dab to check

In it, I invoke ParseBundlerArguments with some _BundlerArguments and EnableAssemblyILStripping preset. I print before and after, and I don't think it overrides (with 0f13eb3).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on ^?

</ParseBundlerArguments>

<PropertyGroup>
Expand Down
35 changes: 35 additions & 0 deletions msbuild/Xamarin.iOS.Tasks.Core/Tasks/ILStripBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

// Normally this would be ILStrip.Tasks but ILStrip is in the global namespace
// And having a type and the parent namespace have the same name really confuses the compiler
namespace ILStripTasks {
public class ILStripBase : ILStrip
{
public string SessionId { get; set; }

[Output]
public ITaskItem [] StrippedAssemblies { get; set; }

public override bool Execute ()
{
var result = base.Execute ();

var stripedItems = new List<ITaskItem> ();

if (result)
{
foreach (var item in Assemblies)
{
stripedItems.Add (new TaskItem (item.GetMetadata("OutputPath")));
}
}

StrippedAssemblies = stripedItems.ToArray();

return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="16.8.0" IncludeAssets="compile" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.8.0" IncludeAssets="compile" />
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="$(MicrosoftNETILLinkTasksPackageVersion)" />
<PackageReference Include="Microsoft.NET.Runtime.MonoTargets.Sdk" Version="$(MicrosoftNETILStripTasksPackageVersion)" GeneratePathProperty="true"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\external\Xamarin.MacDev\Xamarin.MacDev\Xamarin.MacDev.csproj" />
Expand All @@ -24,6 +25,10 @@
<!-- We need the net472 impl, otherwise the Build agent needs to be a net5.0 app -->
<HintPath>$(PkgMicrosoft_NET_ILLink_Tasks)\tools\net472\ILLink.Tasks.dll</HintPath>
</Reference>
<Reference Include="ILStrip">
<!-- We need the net472 impl, otherwise the Build agent needs to be a net5.0 app -->
<HintPath>$(PkgMicrosoft_NET_Runtime_MonoTargets_Sdk)\tasks\net472\ILStrip.dll</HintPath>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, for net7 I'm looking at consolidating the MonoTargets tasks into a single assembly dotnet/runtime#59720

</Reference>
</ItemGroup>
<ItemGroup>
<None Include="NoCode.cs">
Expand Down
25 changes: 25 additions & 0 deletions msbuild/Xamarin.iOS.Tasks/Tasks/ILStrip.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Tasks;
using Microsoft.Build.Framework;
using Xamarin.Messaging.Build.Client;

namespace Xamarin.MacDev.Tasks
{
public class ILStrip : ILStripTasks.ILStripBase, ITaskCallback
{
public override bool Execute ()
{
if (this.ShouldExecuteRemotely (SessionId))
return new TaskRunner (SessionId, BuildEngine4).RunAsync (this).Result;

return base.Execute ();
}

public bool ShouldCopyToBuildServer (ITaskItem item) => false;

public bool ShouldCreateOutputFile (ITaskItem item) => true;

public IEnumerable<ITaskItem> GetAdditionalItemsToBeCopied () => Enumerable.Empty<ITaskItem> ();
}
}
5 changes: 5 additions & 0 deletions msbuild/Xamarin.iOS.Tasks/Xamarin.iOS.Tasks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="16.8.0" IncludeAssets="$(IncludeMSBuildAssets)" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.8.0" IncludeAssets="$(IncludeMSBuildAssets)" />
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="$(MicrosoftNETILLinkTasksPackageVersion)" />
<PackageReference Include="Microsoft.NET.Runtime.MonoTargets.Sdk" Version="$(MicrosoftNETILStripTasksPackageVersion)" GeneratePathProperty="true"/>
<PackageReference Include="Xamarin.Messaging.Build.Client" Version="$(MessagingVersion)" />
</ItemGroup>

Expand All @@ -32,6 +33,10 @@
<Reference Include="ILLink.Tasks">
<HintPath>$(PkgMicrosoft_NET_ILLink_Tasks)\tools\net472\ILLink.Tasks.dll</HintPath>
</Reference>
<Reference Include="ILStrip">
<!-- We need the net472 impl, otherwise the Build agent needs to be a net5.0 app -->
<HintPath>$(PkgMicrosoft_NET_Runtime_MonoTargets_Sdk)\tasks\net472\ILStrip.dll</HintPath>
</Reference>
</ItemGroup>

<ItemGroup>
Expand Down
8 changes: 3 additions & 5 deletions tests/dotnet/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,10 @@ build-dotnet: $(TARGETS)
$(DOTNET6) build size-comparison/MySingleView/dotnet/MySingleView.csproj --runtime ios-arm64 $(COMMON_ARGS) /bl:$@.binlog $(MSBUILD_VERBOSITY)

run-dotnet: $(TARGETS)
$(DOTNET6) build -t:Run size-comparison/MySingleView/dotnet/MySingleView.csproj --runtime ios-arm64 $(COMMON_ARGS)
$(DOTNET6) build -t:Run size-comparison/MySingleView/dotnet/MySingleView.csproj --runtime ios-arm64 $(COMMON_ARGS) /bl:$@.binlog $(MSBUILD_VERBOSITY)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convince mostly. Writing out log is almost free, and being able to test sim was nice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Target strip-dotnet: can also be removed now :)

and the README updated to document how to preserve the IL for analysis (it's already done for legacy)


# this will break the signature, so app won't run anymore. Use it only to compare final size w/legacy
# https://github.com/xamarin/xamarin-macios/issues/11445
strip-dotnet:
$(foreach file, $(wildcard size-comparison/MySingleView/dotnet/bin/iPhone/Release/net6.0-ios/ios-arm64/MySingleView.app/*.dll), mono-cil-strip $(file) $(file);)
run-dotnet-sim: $(TARGETS)
$(DOTNET6) build -t:Run size-comparison/MySingleView/dotnet/MySingleView.csproj /p:Configuration=Release --runtime iossimulator-x64 /p:Platform=iPhoneSimulator /bl:$@.binlog

# this target will copy NuGet.config and global.json to the directories that need it for their .NET build to work correctly.
copy-dotnet-config: $(TARGETS)
Expand Down
9 changes: 1 addition & 8 deletions tests/dotnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,4 @@ Add this option inside the `Release|iPhone` configuration of `size-comparison/My

* net6

**IL stripping is not yet available** so there's nothing to disable right now.

If you want to compare (trimmed) size you can manually call `mono-cil-strip`
on each assembly inside the app bundle.

`make strip-dotnet` will remove the IL from the dotnet app version.
However this is done after the code signature so it will not be possible
to deploy and execute the app afterward. Use for binary analysis only!
Build with `/p:EnableAssemblyILStripping=false` set. The `MtouchExtraArgs` legacy option is also honored.
26 changes: 25 additions & 1 deletion tests/dotnet/UnitTests/PostBuildTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

using Microsoft.Build.Framework;
using Microsoft.Build.Logging.StructuredLogger;
using Mono.Cecil;

namespace Xamarin.Tests {
[TestFixture]
Expand Down Expand Up @@ -50,15 +51,38 @@ public void BuildIpaTest (ApplePlatform platform, string runtimeIdentifiers)
var project = "MySimpleApp";
Configuration.IgnoreIfIgnoredPlatform (platform);

var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath);
var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath, configuration: "Release");
Clean (project_path);
var properties = GetDefaultProperties (runtimeIdentifiers);
properties ["BuildIpa"] = "true";
properties ["Configuration"] = "Release";

DotNet.AssertBuild (project_path, properties);

var pkgPath = Path.Combine (appPath, "..", $"{project}.ipa");
Assert.That (pkgPath, Does.Exist, "pkg creation");

AssertBundleAssembliesStripStatus (appPath, true);
}

[Test]
[TestCase (ApplePlatform.iOS, "ios-arm64", true)]
[TestCase (ApplePlatform.iOS, "ios-arm64", false)]
public void AssemblyStripping (ApplePlatform platform, string runtimeIdentifiers, bool shouldStrip)
{
var project = "MySimpleApp";
Configuration.IgnoreIfIgnoredPlatform (platform);

var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath);
Clean (project_path);
var properties = GetDefaultProperties (runtimeIdentifiers);

// Force EnableAssemblyILStripping since we are building debug which never will by default
properties ["EnableAssemblyILStripping"] = shouldStrip ? "true" : "false";

DotNet.AssertBuild (project_path, properties);

AssertBundleAssembliesStripStatus (appPath, shouldStrip);
}

rolfbjarne marked this conversation as resolved.
Show resolved Hide resolved
[Test]
Expand Down
26 changes: 22 additions & 4 deletions tests/dotnet/UnitTests/TestBaseClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@ protected void SetRuntimeIdentifiers (Dictionary<string, string> properties, str
properties [multiRid] = runtimeIdentifiers;
}

protected string GetProjectPath (string project, string runtimeIdentifiers, ApplePlatform platform, out string appPath, string? subdir = null)
protected string GetProjectPath (string project, string runtimeIdentifiers, ApplePlatform platform, out string appPath, string? subdir = null, string configuration = "Debug")
{
return GetProjectPath (project, null, runtimeIdentifiers, platform, out appPath);
return GetProjectPath (project, null, runtimeIdentifiers, platform, out appPath, configuration);
}

protected string GetProjectPath (string project, string? subdir, string runtimeIdentifiers, ApplePlatform platform, out string appPath)
protected string GetProjectPath (string project, string? subdir, string runtimeIdentifiers, ApplePlatform platform, out string appPath, string configuration = "Debug")
{
var rv = GetProjectPath (project, subdir, platform);
if (string.IsNullOrEmpty (runtimeIdentifiers))
runtimeIdentifiers = GetDefaultRuntimeIdentifier (platform);
var appPathRuntimeIdentifier = runtimeIdentifiers.IndexOf (';') >= 0 ? "" : runtimeIdentifiers;
appPath = Path.Combine (Path.GetDirectoryName (rv)!, "bin", "Debug", platform.ToFramework (), appPathRuntimeIdentifier, project + ".app");
appPath = Path.Combine (Path.GetDirectoryName (rv)!, "bin", configuration, platform.ToFramework (), appPathRuntimeIdentifier, project + ".app");
return rv;
}

Expand Down Expand Up @@ -141,6 +141,24 @@ protected string GetInfoPListPath (ApplePlatform platform, string app_directory)
}
}

protected void AssertBundleAssembliesStripStatus (string appPath, bool shouldStrip)
{
var assemblies = Directory.GetFiles (appPath, "*.dll", SearchOption.AllDirectories);
var assembliesWithOnlyEmptyMethods = new List<String> ();
foreach (var assembly in assemblies) {
ModuleDefinition definition = ModuleDefinition.ReadModule (assembly, new ReaderParameters { ReadingMode = ReadingMode.Deferred });

bool onlyHasEmptyMethods = definition.Assembly.MainModule.Types.All (t =>
t.Methods.Where (m => m.HasBody).All (m => m.Body.Instructions.Count == 1));
if (onlyHasEmptyMethods) {
assembliesWithOnlyEmptyMethods.Add (assembly);
}
}

// Some assemblies, such as Facades, will be completely empty even when not stripped
Assert.That (assemblies.Length == assembliesWithOnlyEmptyMethods.Count, Is.EqualTo (shouldStrip), $"Unexpected stripping status: of {assemblies.Length} assemblies {assembliesWithOnlyEmptyMethods.Count} were empty.");
}

protected string GetNativeExecutable (ApplePlatform platform, string app_directory)
{
var executableName = Path.GetFileNameWithoutExtension (app_directory);
Expand Down