Skip to content

Commit

Permalink
[msbuild] Several updates to the ScnTool task. (#19976)
Browse files Browse the repository at this point in the history
* Enable nullability and fix the resulting issues.
* Convert to XamarinTask (instead of XaxmarinToolTask): this allows us to run multiple
  'scntool' invocations in parallel.
* Use 'xcrun' to call 'scntool' instead of computing the path [1].
* Fix bug in the Cancel method: it shouldn't call base.Execute.
* Change the targets logic to match the pattern of other resource-related targets.
    * This makes it easier to understand the code, since understanding one resource-related target works for the other ones too.
    * Not using the CollectBundleResources task means computing LogicalName in the ScnTool task directly.

[1]: #11172 (comment)
  • Loading branch information
rolfbjarne authored Feb 12, 2024
1 parent 4100f60 commit b9fdd9e
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 113 deletions.
146 changes: 56 additions & 90 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/ScnTool.cs
Original file line number Diff line number Diff line change
@@ -1,147 +1,113 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;

using Xamarin.Messaging.Build.Client;
using Xamarin.Utils;

// Disable until we get around to enable + fix any issues.
#nullable disable

namespace Xamarin.MacDev.Tasks {
public class ScnTool : XamarinToolTask {
string sdkDevPath;

public class ScnTool : XamarinTask {
#region Inputs

[Required]
public string IntermediateOutputPath { get; set; }
public string IntermediateOutputPath { get; set; } = string.Empty;

[Required]
public string InputScene { get; set; }
public ITaskItem [] ColladaAssets { get; set; } = Array.Empty<ITaskItem> ();

[Required]
public string DeviceSpecificIntermediateOutputPath { get; set; } = string.Empty;

public bool IsWatchApp { get; set; }

[Required]
public string OutputScene { get; set; }
public string ProjectDir { get; set; } = string.Empty;

[Required]
public string SdkPlatform { get; set; }
public string ResourcePrefix { get; set; } = string.Empty;

[Required]
public string SdkRoot { get; set; }
public string SdkPlatform { get; set; } = string.Empty;

[Required]
public string SdkVersion { get; set; }
public string SdkRoot { get; set; } = string.Empty;

[Required]
public string SdkDevPath {
get { return sdkDevPath; }
set {
sdkDevPath = value;
public string SdkVersion { get; set; } = string.Empty;

SetEnvironmentVariable ("DEVELOPER_DIR", sdkDevPath);
}
}
[Required]
public string SdkDevPath { get; set; } = string.Empty;

#endregion

string DevicePlatformBinDir {
get { return Path.Combine (SdkDevPath, "usr", "bin"); }
}

protected virtual string OperatingSystem {
get {
return PlatformFrameworkHelper.GetOperatingSystem (TargetFrameworkMoniker);
}
}

protected override string ToolName {
get { return "scntool"; }
}

void SetEnvironmentVariable (string variableName, string value)
{
var envVariables = EnvironmentVariables;
var index = -1;

if (envVariables is null) {
envVariables = new string [1];
index = 0;
} else {
for (int i = 0; i < envVariables.Length; i++) {
if (envVariables [i].StartsWith (variableName + "=", StringComparison.Ordinal)) {
index = i;
break;
}
}

if (index < 0) {
Array.Resize<string> (ref envVariables, envVariables.Length + 1);
index = envVariables.Length - 1;
}
}

envVariables [index] = string.Format ("{0}={1}", variableName, value);

EnvironmentVariables = envVariables;
}

protected override string GenerateFullPathToTool ()
{
if (!string.IsNullOrEmpty (ToolPath))
return Path.Combine (ToolPath, ToolExe);

var path = Path.Combine (DevicePlatformBinDir, ToolExe);

return File.Exists (path) ? path : ToolExe;
}
#region Outputs
[Output]
public ITaskItem [] BundleResources { get; set; } = Array.Empty<ITaskItem> ();
#endregion

protected override string GenerateCommandLineCommands ()
IList<string> GenerateCommandLineCommands (string inputScene, string outputScene)
{
var args = new CommandLineArgumentBuilder ();
var args = new List<string> ();

args.Add ("scntool");
args.Add ("--compress");
args.AddQuoted (InputScene);
args.Add (inputScene);
args.Add ("-o");
args.AddQuoted (OutputScene);
args.AddQuotedFormat ("--sdk-root={0}", SdkRoot);
args.AddQuotedFormat ("--target-build-dir={0}", IntermediateOutputPath);
args.Add (outputScene);
args.Add ($"--sdk-root={SdkRoot}");
args.Add ($"--target-build-dir={IntermediateOutputPath}");
if (AppleSdkSettings.XcodeVersion.Major >= 13) {
// I'm not sure which Xcode version these options are available in, but it's at least Xcode 13+
args.AddQuotedFormat ("--target-version={0}", SdkVersion);
args.AddQuotedFormat ("--target-platform={0}", PlatformUtils.GetTargetPlatform (SdkPlatform, IsWatchApp));
args.Add ($"--target-version={SdkVersion}");
args.Add ($"--target-platform={PlatformUtils.GetTargetPlatform (SdkPlatform, IsWatchApp)}");
} else {
args.AddQuotedFormat ("--target-version-{0}={1}", OperatingSystem, SdkVersion);
args.Add ($"--target-version-{PlatformFrameworkHelper.GetOperatingSystem (TargetFrameworkMoniker)}={SdkVersion}");
}

return args.ToString ();
}

protected override void LogEventsFromTextOutput (string singleLine, MessageImportance messageImportance)
{
// TODO: do proper parsing of error messages and such
Log.LogMessage (messageImportance, "{0}", singleLine);
return args;
}

public override bool Execute ()
{
if (ShouldExecuteRemotely ())
return new TaskRunner (SessionId, BuildEngine4).RunAsync (this).Result;

Directory.CreateDirectory (Path.GetDirectoryName (OutputScene));
var prefixes = BundleResource.SplitResourcePrefixes (ResourcePrefix);
var listOfArguments = new List<(IList<string> Arguments, ITaskItem Input)> ();
var bundleResources = new List<ITaskItem> ();
foreach (var asset in ColladaAssets) {
var inputScene = asset.ItemSpec;
var logicalName = BundleResource.GetLogicalName (ProjectDir, prefixes, asset, !string.IsNullOrEmpty (SessionId));
var outputScene = Path.Combine (DeviceSpecificIntermediateOutputPath, logicalName);
var args = GenerateCommandLineCommands (inputScene, outputScene);
listOfArguments.Add (new (args, asset));

Directory.CreateDirectory (Path.GetDirectoryName (outputScene));

var bundleResource = new TaskItem (outputScene);
asset.CopyMetadataTo (bundleResource);
bundleResource.SetMetadata ("Optimize", "false");
bundleResource.SetMetadata ("LogicalName", logicalName);
bundleResources.Add (bundleResource);
}

Parallel.ForEach (listOfArguments, (arg) => {
ExecuteAsync ("xcrun", arg.Arguments, sdkDevPath: SdkDevPath).Wait ();
});

return base.Execute ();
BundleResources = bundleResources.ToArray ();

return !Log.HasLoggedErrors;
}

public override void Cancel ()
public void Cancel ()
{
if (ShouldExecuteRemotely ())
BuildConnection.CancelAsync (BuildEngine4).Wait ();

base.Execute ();
}
}
}
62 changes: 39 additions & 23 deletions msbuild/Xamarin.Shared/Xamarin.Shared.targets
Original file line number Diff line number Diff line change
Expand Up @@ -1021,9 +1021,11 @@ Copyright (C) 2018 Microsoft. All rights reserved.

<PropertyGroup>
<CompileColladaAssetsDependsOn>
$(CompileColladaAssetsDependsOn);
_RemoveProcessedColladaAssets;
_CollectColladaAssets;
_CoreCompileColladaAssets
_BeforeCoreCompileColladaAssets;
_ReadCoreCompileColladaAssets;
_CoreCompileColladaAssets;
</CompileColladaAssetsDependsOn>
</PropertyGroup>

Expand All @@ -1042,46 +1044,57 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</ItemGroup>
</Target>

<Target Name="_CollectColladaAssets">
<CollectBundleResources
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
BundleResources="@(Collada)"
ProjectDir="$(MSBuildProjectDirectory)"
ResourcePrefix="$(_ResourcePrefix)">
<Output TaskParameter="BundleResourcesWithLogicalNames" ItemName="_ColladaAssetWithLogicalName" />
</CollectBundleResources>
<Target Name="_BeforeCoreCompileColladaAssets"
Inputs="@(Collada)"
Outputs="$(_ColladaCache)">

<!-- If any Collada asset is newer than the generated items list, we delete them so that the _CoreCompileCollada
target runs again and updates those lists for the next run
-->
<Delete Files="$(_ColladaCache)" />
</Target>

<Target Name="_ReadCoreCompileColladaAssets"
DependsOnTargets="_BeforeCoreCompileColladaAssets">

<!-- If _BeforeCoreCompileColladaAssets did not delete the generated items lists from _CoreCompileColladaAssets, then we read them
since that target won't run and we need to the output items that are cached in those files which includes full metadata -->
<ReadItemsFromFile File="$(_ColladaCache)" Condition="Exists('$(_ColladaCache)')">
<Output TaskParameter="Items" ItemName="_BundleResourceWithLogicalName" />
</ReadItemsFromFile>
</Target>

<Target Name="_CoreCompileColladaAssets"
DependsOnTargets="_CollectColladaAssets;_DetectSdkLocations;_ComputeTargetFrameworkMoniker"
Inputs="@(_ColladaAssetWithLogicalName)"
Outputs="$(DeviceSpecificIntermediateOutputPath)%(_ColladaAssetWithLogicalName.LogicalName)"
DependsOnTargets="_BeforeCoreCompileColladaAssets;_DetectSdkLocations;_ComputeTargetFrameworkMoniker"
Inputs="@(Collada)"
Outputs="$(_ColladaCache)"
>

<ScnTool
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
DeviceSpecificIntermediateOutputPath="$(DeviceSpecificIntermediateOutputPath)"
IsWatchApp="$(IsWatchApp)"
ToolExe="$(ScnToolExe)"
ToolPath="$(ScnToolPath)"
ProjectDir="$(MSBuildProjectDirectory)"
ResourcePrefix="$(_ResourcePrefix)"
SdkPlatform="$(_SdkPlatform)"
SdkRoot="$(_SdkRoot)"
SdkDevPath="$(_SdkDevPath)"
SdkVersion="$(_SdkVersion)"
TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)"
IntermediateOutputPath="$(DeviceSpecificIntermediateOutputPath)"
InputScene="%(_ColladaAssetWithLogicalName.Identity)"
OutputScene="$(DeviceSpecificIntermediateOutputPath)%(_ColladaAssetWithLogicalName.LogicalName)">
ColladaAssets="@(Collada)"
>
<Output TaskParameter="BundleResources" ItemName="_BundleResourceWithLogicalName" />
<!-- Local items to be persisted to items files -->
<Output TaskParameter="BundleResources" ItemName="_Collada_BundleResources" />
</ScnTool>

<CreateItem Include="$(DeviceSpecificIntermediateOutputPath)%(_ColladaAssetWithLogicalName.LogicalName)" AdditionalMetadata="LogicalName=%(_ColladaAssetWithLogicalName.LogicalName);Optimize='False'">
<Output TaskParameter="Include" ItemName="_BundleResourceWithLogicalName" />
</CreateItem>

<WriteItemsToFile Items="@(_Collada_BundleResources)" ItemName="_BundleResourceWithLogicalName" File="$(_ColladaCache)" Overwrite="true" IncludeMetadata="true" />
<!-- Write out the list of assets we've processed, so that an inner build in a multi-rid build can skip processing them -->
<WriteItemsToFile Items="@(_ColladaAssetWithLogicalName)" Condition="'$(_SaveProcessedItems)' == 'true'" ItemName="_ColladaAssetWithLogicalName" File="$(_ProcessedColladaAssetsPath)" Overwrite="true" IncludeMetadata="false" />
<WriteItemsToFile Items="@(Collada)" Condition="'$(_SaveProcessedItems)' == 'true'" ItemName="Collada" File="$(_ProcessedColladaAssetsPath)" Overwrite="true" IncludeMetadata="false" />
<ItemGroup>
<FileWrites Include="$(_ColladaCache)" />
<FileWrites Include="$(_ProcessedColladaAssetsPath)" />
</ItemGroup>
</Target>
Expand Down Expand Up @@ -1137,6 +1150,9 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<!-- TextureAtlas output caches -->
<_TextureAtlasCache>$(DeviceSpecificIntermediateOutputPath)atlas\_BundleResourceWithLogicalName.items</_TextureAtlasCache>

<!-- Collada output caches -->
<_ColladaCache>$(DeviceSpecificIntermediateOutputPath)collada\_BundleResourceWithLogicalName.items</_ColladaCache>

<!-- processed items -->
<_ProcessedBundleResourcesPath Condition="'$(_ProcessedBundleResourcesPath)' == ''">$(DeviceSpecificIntermediateOutputPath)\_ProcessedBundleResourcesPath.items</_ProcessedBundleResourcesPath>
<_ProcessedContentPath Condition="'$(_ProcessedContentPath)' == ''">$(DeviceSpecificIntermediateOutputPath)\_ProcessedContentPath.items</_ProcessedContentPath>
Expand Down

10 comments on commit b9fdd9e

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

Please sign in to comment.