[msbuild] Rework how the app manifest is created.

How we create the app manifest (Info.plist) has to be modified so that we can add
support for getting all the values from MSBuild properties (i.e. no Info.plist in
the project), as well as having multiple partial app manifests as well, that gets
merged into the final app manifest.

Here's the new process:

1. The user can specify values in multiple ways:

    * An Info.plist in their project file (by using a `None` item with
      filename "Info.plist" or with a `Link` metadata with filename
      "Info.plist"). We figure this out in the DetectAppManifest target.
    * A partial plist in their project (using the `PartialAppManifest` item group)
    * Some MSBuild properties can also add values.

    The precedence is: MSBuild properties can be overridden by the Info.plist,
    which can be overridden by a partial plist.

2. In the `CompileAppManifest` target we get all the inputs from above, and compute
a temporary app manifest, which is written to a temporary output file.

3. In the `ReadAppManifest` target, we read the temporary output file and outputs
numerous MSBuild properties (most of then private)

4. We run other targets that may add more entries to the final app manifest (these
tasks might depend on the values from `ReadAppManifest`). These entries are written
to partial plists, and added to the _PostCompilePartialAppManifest item group.

   The targets in question are:

	* _CompileImageAssets * _CompileCoreMLModels

5. In the new `WriteAppManifest` target, we read the temporary output file from `ReadAppManifest`
+ any `_PartialAppManfiest` items and merge them all together to get the final Info.plist.

This also required moving the computation of CFBundleIdentifier from the DetectSigningIdentity
task to the CompileAppManifest task. This also meant reordering these two tasks,
so that the DetectSigningIdentity task is executed after the CompileAppManifest task
(technically after the ReadAppManifest task), because the DetectSigningIdentity task
needs to know the bundle identifier.

This way we can handle multiple scenarios easily (most of this is not covered by
these changes, and will be implemented separately):

* No Info.plist at all, all non-default values come from MSBuild properties.
* A single Info.plist, where everything is specified.
* An Info.plist with multiple partial app manifests as well.
This commit is contained in:
Rolf Bjarne Kvinge 2021-08-04 12:42:43 +02:00
Родитель fa8e792040
Коммит b3dff34ea5
14 изменённых файлов: 291 добавлений и 142 удалений

Просмотреть файл

@ -196,7 +196,9 @@
<BuildDependsOn Condition="'$(RuntimeIdentifiers)' != ''">
_RunRidSpecificBuild;
_CompileEntitlements;
_CompileAppManifest;
_DetectAppManifest;
_ReadAppManifest;
_WriteAppManifest;
_CreateMergedAppBundle;
Codesign;
CreateIpa;
@ -209,7 +211,6 @@
<!-- single-rid build (either plain single, or inner build for multi-rid build) -->
<CreateAppBundleDependsOn Condition="'$(RuntimeIdentifiers)' == ''">
$(CreateAppBundleDependsOn);
_DetectAppManifest;
_CopyResourcesToBundle;
_CompileCoreMLModels;
_CreatePkgInfo;
@ -217,7 +218,9 @@
_ForgeMetal;
_TemperMetal;
_CompileEntitlements;
_CompileAppManifest;
_DetectAppManifest;
_ReadAppManifest;
_WriteAppManifest;
_ResolveAppExtensionReferences;
_ExtendAppExtensionReferences;
_ComputeLinkerArguments;
@ -369,6 +372,9 @@
_CreateRuntimeConfiguration;
_ComputeMonoLibraries;
_FindAotCompiler;
_DetectAppManifest;
_ReadAppManifest;
_WriteAppManifest;
</_ComputeLinkerArgumentsDependsOn>
</PropertyGroup>
@ -809,6 +815,9 @@
$(_AOTCompileDependsOn);
_ComputeVariables;
_FindAotCompiler;
_DetectAppManifest;
_ReadAppManifest;
_WriteAppManifest;
</_AOTCompileDependsOn>
</PropertyGroup>
<Target Name="_AOTCompile"
@ -894,7 +903,9 @@
_DetectSdkLocations;
_ComputeTargetArchitectures;
_GenerateBundleName;
_DetectAppManifest;
_ReadAppManifest;
_WriteAppManifest;
_ComputeNativeExecutableInputs;
_AOTCompile;
</_CompileNativeExecutableDependsOn>
@ -931,7 +942,9 @@
_DetectSdkLocations;
_ComputeTargetArchitectures;
_GenerateBundleName;
_CompileAppManifest;
_DetectAppManifest;
_ReadAppManifest;
_WriteAppManifest;
_CompileNativeExecutable;
</_LinkNativeExecutableDependsOn>
</PropertyGroup>

Просмотреть файл

@ -54,7 +54,7 @@ Copyright (C) 2014 Xamarin. All rights reserved.
_ForgeMetal;
_TemperMetal;
_CompileEntitlements;
_CompileAppManifest;
_WriteAppManifest;
_ParseBundlerArguments;
_CompileToNative;
_CreatePkgInfo;
@ -98,7 +98,6 @@ Copyright (C) 2014 Xamarin. All rights reserved.
<PropertyGroup>
<_CompileTextureAtlasesDependsOn>
$(_CompileTextureAtlasesDependsOn);
_DetectAppManifest;
_DetectSdkLocations;
_CoreCompileTextureAtlases;
</_CompileTextureAtlasesDependsOn>
@ -128,12 +127,11 @@ Copyright (C) 2014 Xamarin. All rights reserved.
<CompileToNativeDependsOn>
_ComputeLinkMode;
_ComputeTargetFrameworkMoniker;
_DetectAppManifest;
_DetectSdkLocations;
_GenerateBundleName;
ResolveReferences;
_CompileEntitlements;
_CompileAppManifest;
_WriteAppManifest;
_GetNativeExecutableName;
_GetCompileToNativeInputs;
_ExpandNativeReferences;

Просмотреть файл

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Framework;
@ -13,6 +14,9 @@ namespace Xamarin.MacDev.Tasks
{
#region Inputs
// Single-project property that maps to CFBundleIdentifier for Apple platforms
public string ApplicationId { get; set; }
// Single-project property that maps to CFBundleShortVersionString for Apple platforms
public string AppleShortVersion { get; set; }
@ -25,15 +29,11 @@ namespace Xamarin.MacDev.Tasks
[Required]
public string AppBundleName { get; set; }
[Required]
public string AppManifest { get; set; }
[Required]
public string AssemblyName { get; set; }
[Required]
public string BundleIdentifier { get; set; }
[Required]
[Output] // This is required to create an empty file on Windows for the Input/Outputs check.
public ITaskItem CompiledAppManifest { get; set; }
@ -58,9 +58,6 @@ namespace Xamarin.MacDev.Tasks
public bool IsWatchExtension { get; set; }
[Required]
public string MinimumOSVersion { get; set; }
public ITaskItem[] PartialAppManifests { get; set; }
public string ResourceRules { get; set; }
@ -71,6 +68,9 @@ namespace Xamarin.MacDev.Tasks
[Required]
public bool SdkIsSimulator { get; set; }
[Required]
public string SdkVersion { get; set; }
public string TargetArchitectures { get; set; }
#endregion
@ -78,23 +78,26 @@ namespace Xamarin.MacDev.Tasks
public override bool Execute ()
{
PDictionary plist;
PDictionary plist = null;
try {
plist = PDictionary.FromFile (AppManifest);
} catch (Exception ex) {
LogAppManifestError (MSBStrings.E0010, AppManifest, ex.Message);
return false;
if (File.Exists (AppManifest)) {
try {
plist = PDictionary.FromFile (AppManifest);
} catch (Exception ex) {
LogAppManifestError (MSBStrings.E0010, AppManifest, ex.Message);
return false;
}
} else {
plist = new PDictionary ();
}
plist.SetIfNotPresent (PlatformFrameworkHelper.GetMinimumOSVersionKey (Platform), MinimumOSVersion);
if (!string.IsNullOrEmpty (TargetArchitectures) && !Enum.TryParse (TargetArchitectures, out architectures)) {
LogAppManifestError (MSBStrings.E0012, TargetArchitectures);
return false;
}
plist.SetCFBundleIdentifier (BundleIdentifier); // no ifs and buts, we've computed the final bundle identifier (BundleIdentifier) in DetectSigningIdentityTask.
if (GenerateApplicationManifest && !string.IsNullOrEmpty (ApplicationId))
plist.SetIfNotPresent (ManifestKeys.CFBundleIdentifier, ApplicationId);
plist.SetIfNotPresent (ManifestKeys.CFBundleInfoDictionaryVersion, "6.0");
plist.SetIfNotPresent (ManifestKeys.CFBundlePackageType, IsAppExtension ? "XPC!" : "APPL");
plist.SetIfNotPresent (ManifestKeys.CFBundleSignature, "????");
@ -119,18 +122,49 @@ namespace Xamarin.MacDev.Tasks
if (string.IsNullOrEmpty (defaultBundleShortVersion))
defaultBundleShortVersion = plist.GetCFBundleVersion ();
plist.SetIfNotPresent (ManifestKeys.CFBundleShortVersionString, defaultBundleShortVersion);
if (!SetMinimumOSVersion (plist))
return false;
if (!Compile (plist))
return false;
// Merge with any partial plists...
MergePartialPlistTemplates (plist);
plist.Save (CompiledAppManifest.ItemSpec, true, true);
// write the resulting app manifest
if (FileUtils.UpdateFile (CompiledAppManifest.ItemSpec, (tmpfile) => plist.Save (tmpfile, true, true)))
Log.LogMessage (MessageImportance.Low, "The file {0} is up-to-date.", CompiledAppManifest.ItemSpec);
return !Log.HasLoggedErrors;
}
bool SetMinimumOSVersion (PDictionary plist)
{
var minimumOSVersionInManifest = plist?.Get<PString> (PlatformFrameworkHelper.GetMinimumOSVersionKey (Platform))?.Value;
string minimumOSVersion;
if (string.IsNullOrEmpty (minimumOSVersionInManifest)) {
minimumOSVersion = SdkVersion;
} else if (!IAppleSdkVersion_Extensions.TryParse (minimumOSVersionInManifest, out var _)) {
Log.LogError (null, null, null, AppManifest, 0, 0, 0, 0, MSBStrings.E0011, minimumOSVersionInManifest);
return false;
} else {
minimumOSVersion = minimumOSVersionInManifest;
}
if (Platform == ApplePlatform.MacCatalyst) {
// Convert the min macOS version to the min iOS version, which the rest of our tooling expects.
if (!MacCatalystSupport.TryGetiOSVersion (Sdks.GetAppleSdk (Platform).GetSdkPath (SdkVersion, false), minimumOSVersion, out var convertedVersion))
Log.LogError (MSBStrings.E0187, minimumOSVersion);
minimumOSVersion = convertedVersion;
}
// Write out our value
plist [PlatformFrameworkHelper.GetMinimumOSVersionKey (Platform)] = minimumOSVersion;
return true;
}
protected abstract bool Compile (PDictionary plist);
protected void LogAppManifestError (string format, params object[] args)
@ -156,7 +190,7 @@ namespace Xamarin.MacDev.Tasks
dict[key] = value;
}
protected void MergePartialPlistDictionary (PDictionary plist, PDictionary partial)
public static void MergePartialPlistDictionary (PDictionary plist, PDictionary partial)
{
foreach (var property in partial) {
if (plist.ContainsKey (property.Key)) {
@ -173,23 +207,28 @@ namespace Xamarin.MacDev.Tasks
}
}
protected void MergePartialPlistTemplates (PDictionary plist)
public static void MergePartialPLists (Task task, PDictionary plist, IEnumerable<ITaskItem> partialLists)
{
if (PartialAppManifests == null)
if (partialLists == null)
return;
foreach (var template in PartialAppManifests) {
foreach (var template in partialLists) {
PDictionary partial;
try {
partial = PDictionary.FromFile (template.ItemSpec);
} catch (Exception ex) {
Log.LogError (MSBStrings.E0107, template.ItemSpec, ex.Message);
task.Log.LogError (MSBStrings.E0107, template.ItemSpec, ex.Message);
continue;
}
MergePartialPlistDictionary (plist, partial);
}
}
protected void MergePartialPlistTemplates (PDictionary plist)
{
MergePartialPLists (this, plist, PartialAppManifests);
}
}
}

Просмотреть файл

@ -102,14 +102,7 @@ namespace Xamarin.MacDev.Tasks
[Required]
public string AppBundleName { get; set; }
[Required]
public string AppManifest { get; set; }
// Single-project property that maps to CFBundleIdentifier for Apple platforms
public string ApplicationId { get; set; }
// Single-project property that determines whether other single-project properties should have any effect
public bool GenerateApplicationManifest { get; set; }
public string BundleIdentifier { get; set; }
public string Keychain { get; set; }
@ -133,9 +126,6 @@ namespace Xamarin.MacDev.Tasks
[Output]
public string DetectedAppId { get; set; }
[Output]
public string DetectedBundleId { get; set; }
// This is input too
[Output]
public string DetectedCodeSigningKey { get; set; }
@ -240,7 +230,7 @@ namespace Xamarin.MacDev.Tasks
Log.LogMessage (MessageImportance.High, " Code Signing Key: \"{0}\" ({1})", codesignCommonName, DetectedCodeSigningKey);
if (provisioningProfileName != null)
Log.LogMessage (MessageImportance.High, " Provisioning Profile: \"{0}\" ({1})", provisioningProfileName, DetectedProvisioningProfile);
Log.LogMessage (MessageImportance.High, " Bundle Id: {0}", DetectedBundleId);
Log.LogMessage (MessageImportance.High, " Bundle Id: {0}", BundleIdentifier);
Log.LogMessage (MessageImportance.High, " App Id: {0}", DetectedAppId);
}
@ -451,7 +441,7 @@ namespace Xamarin.MacDev.Tasks
}
if (matches.Count == 0) {
Log.LogWarning (null, null, null, AppManifest, 0, 0, 0, 0, MSBStrings.W0137);
Log.LogWarning (MSBStrings.W0137);
return identity;
}
@ -481,7 +471,6 @@ namespace Xamarin.MacDev.Tasks
IList<MobileProvision> profiles;
IList<X509Certificate2> certs;
List<CodeSignIdentity> pairs;
PDictionary plist;
switch (SdkPlatform) {
case "AppleTVSimulator":
@ -512,27 +501,11 @@ namespace Xamarin.MacDev.Tasks
else if (ProvisioningProfile == AutomaticAdHocProvision)
type = MobileProvisionDistributionType.AdHoc;
try {
plist = PDictionary.FromFile (AppManifest);
} catch (Exception ex) {
Log.LogError (null, null, null, AppManifest, 0, 0, 0, 0, MSBStrings.E0010, AppManifest, ex.Message);
return false;
}
DetectedCodesignAllocate = Path.Combine (DeveloperRoot, "Toolchains", "XcodeDefault.xctoolchain", "usr", "bin", "codesign_allocate");
DetectedDistributionType = type.ToString ();
identity.BundleId = plist.GetCFBundleIdentifier ();
if (string.IsNullOrEmpty (identity.BundleId)) {
if (GenerateApplicationManifest && !string.IsNullOrEmpty (ApplicationId)) {
identity.BundleId = ApplicationId;
} else {
Log.LogError (null, null, null, AppManifest, 0, 0, 0, 0, MSBStrings.E0139, AppManifest);
return false;
}
}
DetectedBundleId = identity.BundleId;
DetectedAppId = DetectedBundleId; // default value that can be changed below
identity.BundleId = BundleIdentifier;
DetectedAppId = BundleIdentifier; // default value that can be changed below
if (Platform == ApplePlatform.MacOSX) {
if (!RequireCodeSigning || !string.IsNullOrEmpty (DetectedCodeSigningKey)) {
@ -570,7 +543,7 @@ namespace Xamarin.MacDev.Tasks
identity.AppId = ConstructValidAppId (identity.Profile, identity.BundleId);
if (identity.AppId == null) {
Log.LogError (null, null, null, AppManifest, 0, 0, 0, 0, MSBStrings.E0141, identity.BundleId, ProvisioningProfile);
Log.LogError (MSBStrings.E0141, identity.BundleId, ProvisioningProfile);
return false;
}
@ -659,7 +632,7 @@ namespace Xamarin.MacDev.Tasks
identity.AppId = ConstructValidAppId (identity.Profile, identity.BundleId);
if (identity.AppId == null) {
Log.LogError (null, null, null, AppManifest, 0, 0, 0, 0, MSBStrings.E0141, identity.BundleId, ProvisioningProfile);
Log.LogError (MSBStrings.E0141, identity.BundleId, ProvisioningProfile);
return false;
}

Просмотреть файл

@ -17,9 +17,15 @@ namespace Xamarin.MacDev.Tasks {
[Output]
public string? CLKComplicationGroup { get; set; }
[Output]
public string? CFBundleExecutable { get; set; }
[Output]
public string? CFBundleDisplayName { get; set; }
[Output]
public string? CFBundleIdentifier { get; set; }
[Output]
public string? CFBundleVersion { get; set; }
@ -54,26 +60,12 @@ namespace Xamarin.MacDev.Tasks {
}
}
var minimumOSVersionInManifest = plist?.Get<PString> (PlatformFrameworkHelper.GetMinimumOSVersionKey (Platform))?.Value;
if (string.IsNullOrEmpty (minimumOSVersionInManifest)) {
MinimumOSVersion = SdkVersion;
} else if (!IAppleSdkVersion_Extensions.TryParse (minimumOSVersionInManifest, out var _)) {
Log.LogError (null, null, null, AppManifest?.ItemSpec, 0, 0, 0, 0, MSBStrings.E0011, minimumOSVersionInManifest);
return false;
} else {
MinimumOSVersion = minimumOSVersionInManifest;
}
if (Platform == ApplePlatform.MacCatalyst) {
// Convert the min macOS version to the min iOS version, which the rest of our tooling expects.
if (!MacCatalystSupport.TryGetiOSVersion (Sdks.GetAppleSdk (Platform).GetSdkPath (SdkVersion, false), MinimumOSVersion, out var convertedVersion))
Log.LogError (MSBStrings.E0187, MinimumOSVersion);
MinimumOSVersion = convertedVersion;
}
CFBundleExecutable = plist.GetCFBundleExecutable ();
CFBundleDisplayName = plist?.GetCFBundleDisplayName ();
CFBundleIdentifier = plist?.GetCFBundleIdentifier ();
CFBundleVersion = plist?.GetCFBundleVersion ();
CLKComplicationGroup = plist?.Get<PString> (ManifestKeys.CLKComplicationGroup)?.Value;
MinimumOSVersion = plist?.Get<PString> (PlatformFrameworkHelper.GetMinimumOSVersionKey (Platform))?.Value;
NSExtensionPointIdentifier = plist?.GetNSExtensionPointIdentifier ();
UIDeviceFamily = plist?.GetUIDeviceFamily ().ToString ();
WKWatchKitApp = plist?.GetWKWatchKitApp () == true;

Просмотреть файл

@ -0,0 +1,51 @@
using System;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Xamarin.Localization.MSBuild;
using Xamarin.Utils;
namespace Xamarin.MacDev.Tasks
{
public abstract class WriteAppManifestTaskBase : XamarinTask
{
#region Inputs
[Required]
public string AppBundleManifest { get; set; }
[Required]
public ITaskItem[] AppManifests { get; set; }
#endregion
public override bool Execute ()
{
PDictionary plist;
var firstManifest = AppManifests [0].ItemSpec;
try {
plist = PDictionary.FromFile (firstManifest);
} catch (Exception ex) {
Log.LogError (null, null, null, firstManifest, 0, 0, 0, 0, MSBStrings.E0010, ex.Message);
return false;
}
if (AppManifests.Length >1)
CompileAppManifestTaskBase.MergePartialPLists (this, plist, AppManifests.Skip (1));
// Remove any IDE specific keys we don't want in the final app manifest.
plist.Remove (ManifestKeys.XSLaunchImageAssets);
plist.Remove (ManifestKeys.XSAppIconAssets);
// write the resulting app manifest
Directory.CreateDirectory (Path.GetDirectoryName (AppBundleManifest));
plist.Save (AppBundleManifest, true, true);
return !Log.HasLoggedErrors;
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
using Microsoft.Build.Framework;
using Xamarin.Messaging.Build.Client;
namespace Xamarin.MacDev.Tasks
{
public class WriteAppManifest : WriteAppManifestTaskBase, ICancelableTask
{
public override bool Execute ()
{
if (ShouldExecuteRemotely ())
return new TaskRunner (SessionId, BuildEngine4).RunAsync (this).Result;
return base.Execute ();
}
public void Cancel ()
{
if (ShouldExecuteRemotely ())
BuildConnection.CancelAsync (SessionId, BuildEngine4).Wait ();
}
}
}

Просмотреть файл

@ -110,7 +110,6 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<UsingTask TaskName="Xamarin.MacDev.Tasks.GetDirectories" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.GetFileSystemEntries" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.GetFullPath" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.GetNativeExecutableName" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.GetPropertyListValue" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.IBTool" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.PackLibraryResources" AssemblyFile="$(_TaskAssemblyName)" />
@ -124,6 +123,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<UsingTask TaskName="Xamarin.MacDev.Tasks.SpotlightIndexer" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.SymbolStrip" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.UnpackLibraryResources" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.WriteAppManifest" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.WriteItemsToFile" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask TaskName="Xamarin.MacDev.Tasks.Zip" AssemblyFile="$(_TaskAssemblyName)" />
@ -270,33 +270,58 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</CollectBundleResources>
</Target>
<!--
The process to create the final app manifest (aka Info.plist) is a bit convoluted.
1. The user can specify values in multiple ways:
* An Info.plist in their project file (by using a `None` item with filename "Info.plist" or with a `Link` metadata with filename "Info.plist"). We figure this out in the DetectAppManifest target.
* A partial plist in their project (using the `PartialAppManifest` item group)
* Some MSBuild properties can also add values.
The precedence is: MSBuild properties can be overridden by the Info.plist, which can be overridden by a partial plist.
2. In the `CompileAppManifest` target we get all the inputs from above, and compute a temporary app manifest, which is written to a temporary output file.
3. In the `ReadAppManifest` target, we read the temporary output file and outputs numerous MSBuild properties (most of then private)
4. We run some other tasks, that depend on the values from `ReadAppManifest`, and adds more entries that should be in the final app manifest. These are written to partial plists, and added to the _PostCompilePartialAppManifest item group.
* _CompileImageAssets
* _CompileCoreMLModels
5. In the `WriteAppManifest` target, we read the temporary output file from `ReadAppManifest` + any `_PartialAppManfiest` items and merge them all together to get the final Info.plist.
-->
<PropertyGroup>
<_CompileAppManifestDependsOn>
$(_CompileAppManifestDependsOn);
_DetectAppManifest;
_DetectSdkLocations;
_ReadAppManifest;
_GenerateBundleName;
_DetectSigningIdentity;
_ComputeTargetFrameworkMoniker;
_ComputeTargetArchitectures;
</_CompileAppManifestDependsOn>
</PropertyGroup>
<!-- This target has no inputs, because it must still be run if there are no input app manifests, to set default values -->
<Target Name="_CompileAppManifest"
DependsOnTargets="$(_CompileAppManifestDependsOn)"
Inputs="$(_AppManifest);@(PartialAppManifest)"
Outputs="$(_AppBundleManifest)" >
Outputs="$(_TemporaryAppManifest)"
>
<CompileAppManifest
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
AppleShortVersion="$(AppleShortVersion)"
ApplicationId="$(ApplicationId)"
ApplicationTitle="$(ApplicationTitle)"
ApplicationVersion="$(ApplicationVersion)"
AppBundleName="$(_AppBundleName)"
AppManifest="$(_AppManifest)"
AssemblyName="$(AssemblyName)"
BundleIdentifier="$(_BundleIdentifier)"
CompiledAppManifest="$(_AppBundleManifest)"
CompiledAppManifest="$(_TemporaryAppManifest)"
Debug="$(_BundlerDebug)"
DefaultSdkVersion="$(_SdkVersion)"
GenerateApplicationManifest="$(GenerateApplicationManifest)"
@ -304,19 +329,16 @@ Copyright (C) 2018 Microsoft. All rights reserved.
IsWatchApp="$(IsWatchApp)"
IsWatchExtension="$(IsWatchExtension)"
IsXPCService="$(IsXPCService)"
MinimumOSVersion="$(_MinimumOSVersion)"
PartialAppManifests="@(PartialAppManifest)"
ResourceRules="$(_PreparedResourceRules)"
TargetArchitectures="$(TargetArchitectures)"
TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)"
SdkPlatform="$(_SdkPlatform)"
SdkIsSimulator="$(_SdkIsSimulator)"
SdkVersion="$(_SdkVersion)"
DebugIPAddresses="$(_DebugIPAddresses)"
>
</CompileAppManifest>
<RemoveDir SessionId="$(BuildSessionId)" Condition="'$(IsMacEnabled)' == 'true'" Directories="$(AppBundleDir).dSYM" />
<Delete SessionId="$(BuildSessionId)" Condition="'$(IsMacEnabled)' == 'true'" Files="$(DeviceSpecificOutputPath)*.bcsymbolmap" />
</Target>
<PropertyGroup>
@ -324,6 +346,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
$(_ReadAppManifestDependsOn);
_DetectAppManifest;
_DetectSdkLocations;
_CompileAppManifest;
_ComputeTargetFrameworkMoniker;
</_ReadAppManifestDependsOn>
</PropertyGroup>
@ -332,13 +355,15 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<ReadAppManifest
Condition="'$(IsMacEnabled)' == 'true'"
SessionId="$(BuildSessionId)"
AppManifest="$(_AppManifest)"
AppManifest="$(_TemporaryAppManifest)"
SdkVersion="$(_SdkVersion)"
TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)"
>
<Output TaskParameter="CLKComplicationGroup" PropertyName="_CLKComplicationGroup" />
<Output TaskParameter="CFBundleExecutable" PropertyName="_ExecutableName" />
<Output TaskParameter="CFBundleDisplayName" PropertyName="_CFBundleDisplayName" />
<Output TaskParameter="CFBundleIdentifier" PropertyName="_BundleIdentifier" />
<Output TaskParameter="CFBundleVersion" PropertyName="_CFBundleVersion" />
<Output TaskParameter="CLKComplicationGroup" PropertyName="_CLKComplicationGroup" />
<Output TaskParameter="MinimumOSVersion" PropertyName="_MinimumOSVersion" />
<Output TaskParameter="NSExtensionPointIdentifier" PropertyName="_NSExtensionPointIdentifier" />
<Output TaskParameter="UIDeviceFamily" PropertyName="_UIDeviceFamily" />
@ -348,6 +373,34 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</ReadAppManifest>
</Target>
<PropertyGroup>
<_WriteAppManifestDependsOn>
_CompileAppManifest;
_CompileImageAssets;
_CompileCoreMLModels;
</_WriteAppManifestDependsOn>
</PropertyGroup>
<!-- This target will create the $(_AppBundleManifest) file - any task that takes $(_AppBundleManifest) must depend on this target -->
<Target Name="_WriteAppManifest"
Condition="'$(_CreateAppManifest)' == 'true'"
DependsOnTargets="$(_WriteAppManifestDependsOn)"
Inputs="@(_PostCompilePartialAppManifest);$(_TemporaryAppManifest)"
Outputs="$(_AppBundleManifest)"
>
<WriteAppManifest
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
AppBundleManifest="$(_AppBundleManifest)"
AppManifests="@(_PostCompilePartialAppManifest);$(_TemporaryAppManifest)"
>
</WriteAppManifest>
<RemoveDir SessionId="$(BuildSessionId)" Condition="'$(IsMacEnabled)' == 'true'" Directories="$(AppBundleDir).dSYM" />
<Delete SessionId="$(BuildSessionId)" Condition="'$(IsMacEnabled)' == 'true'" Files="$(DeviceSpecificOutputPath)*.bcsymbolmap" />
</Target>
<PropertyGroup>
<_CompileEntitlementsDependsOn>
$(_CompileEntitlementsDependsOn);
@ -355,6 +408,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
_GenerateBundleName;
_DetectSigningIdentity;
_ComputeTargetFrameworkMoniker;
_ReadAppManifest;
</_CompileEntitlementsDependsOn>
</PropertyGroup>
@ -466,8 +520,10 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<Target Name="_CompileImageAssets" DependsOnTargets="_DetectAppManifest;_DetectSdkLocations;_ComputeTargetArchitectures;_BeforeCoreCompileImageAssets;_ReadCompileImageAssets;_CoreCompileImageAssets" />
<Target Name="_BeforeCoreCompileImageAssets"
Inputs="@(ImageAsset);$(_AppManifest)"
Outputs="$(_ACTool_PartialAppManifestCache);$(_ACTool_BundleResourceCache)">
Inputs="@(ImageAsset);$(_TemporaryAppManifest)"
Outputs="$(_ACTool_PartialAppManifestCache);$(_ACTool_BundleResourceCache)"
DependsOnTargets="_ReadAppManifest"
>
<!-- If any ImageAsset or AppManifest is newer than the generated items list, we delete them so that the _CoreCompileImageAssets
target runs again and updates those lists for the next run
@ -482,7 +538,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<!-- If _BeforeCoreCompileImageAssets did not delete the generated items lists from _CoreCompileImageAsset, then we read them
since that target won't run and we need the output items that are cached in those files, which includes full metadata -->
<ReadItemsFromFile File="$(_ACTool_PartialAppManifestCache)" Condition="Exists('$(_ACTool_PartialAppManifestCache)')">
<Output TaskParameter="Items" ItemName="PartialAppManifest" />
<Output TaskParameter="Items" ItemName="_PostCompilePartialAppManifest" />
</ReadItemsFromFile>
<ReadItemsFromFile File="$(_ACTool_BundleResourceCache)" Condition="Exists('$(_ACTool_BundleResourceCache)')">
<Output TaskParameter="Items" ItemName="_BundleResourceWithLogicalName" />
@ -492,7 +548,6 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<PropertyGroup>
<_CoreCompileImageAssetsDependsOn>
$(_CoreCompileImageAssets);
_DetectAppManifest;
_ReadAppManifest;
_DetectSdkLocations;
_BeforeCoreCompileImageAssets;
@ -502,7 +557,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</PropertyGroup>
<Target Name="_CoreCompileImageAssets"
Inputs="@(ImageAsset);$(_AppManifest)"
Inputs="@(ImageAsset);$(_TemporaryAppManifest)"
Outputs="$(_ACTool_PartialAppManifestCache);$(_ACTool_BundleResourceCache)"
DependsOnTargets="$(_CoreCompileImageAssetsDependsOn)">
@ -536,7 +591,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
XSAppIconAssets="$(_XSAppIconAssets)"
XSLaunchImageAssets="$(_XSLaunchImageAssets)"
>
<Output TaskParameter="PartialAppManifest" ItemName="PartialAppManifest" />
<Output TaskParameter="PartialAppManifest" ItemName="_PostCompilePartialAppManifest" />
<Output TaskParameter="BundleResources" ItemName="_BundleResourceWithLogicalName" />
<!-- Local items to be persisted to items files -->
@ -545,7 +600,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</ACTool>
<!-- Cache the generated outputs items for incremental build support -->
<WriteItemsToFile Items="@(_ACTool_PartialAppManifest)" ItemName="PartialAppManifest" File="$(_ACTool_PartialAppManifestCache)" Overwrite="true" IncludeMetadata="true" />
<WriteItemsToFile Items="@(_ACTool_PartialAppManifest)" ItemName="_PostCompilePartialAppManifest" File="$(_ACTool_PartialAppManifestCache)" Overwrite="true" IncludeMetadata="true" />
<WriteItemsToFile Items="@(_ACTool_BundleResources)" ItemName="_BundleResourceWithLogicalName" File="$(_ACTool_BundleResourceCache)" Overwrite="true" IncludeMetadata="true" />
<ItemGroup>
<FileWrites Include="$(_ACTool_PartialAppManifestCache);$(_ACTool_BundleResourceCache)" />
@ -708,7 +763,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</Target>
<!-- TODO: check for duplicate items -->
<Target Name="_ComputeBundleResourceOutputPaths" DependsOnTargets="_CollectBundleResources;_GenerateBundleName;_DetectSigningIdentity">
<Target Name="_ComputeBundleResourceOutputPaths" DependsOnTargets="_CollectBundleResources;_GenerateBundleName;_DetectSigningIdentity;_ReadAppManifest">
<ComputeBundleResourceOutputPaths
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
@ -750,7 +805,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<Target Name="_CompileCoreMLModels" DependsOnTargets="_DetectAppManifest;_DetectSdkLocations;_ComputeTargetArchitectures;_BeforeCompileCoreMLModels;_ReadCompileCoreMLModels;_CoreCompileCoreMLModels" />
<Target Name="_BeforeCompileCoreMLModels"
Inputs="@(CoreMLModel);$(_AppManifest)"
Inputs="@(CoreMLModel);$(_TemporaryAppManifest)"
Outputs="$(_CoreMLModel_PartialAppManifestCache);$(_CoreMLModel_BundleResourceCache)">
<!-- If any CoreMLModel or AppManifest is newer than the generated items list, we delete them so that the _CoreCompileCoreMLModels
target runs again and updates those lists for the next run
@ -763,7 +818,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<!-- If _BeforeCompileCoreMLModels did not delete the generated items lists from _CoreCompileCoreMLModels, then we read them
since that target won't run and we need the output items that are cached in those files, which includes full metadata -->
<ReadItemsFromFile File="$(_CoreMLModel_PartialAppManifestCache)" Condition="Exists('$(_CoreMLModel_PartialAppManifestCache)')">
<Output TaskParameter="Items" ItemName="PartialAppManifest" />
<Output TaskParameter="Items" ItemName="_PostCompilePartialAppManifest" />
</ReadItemsFromFile>
<ReadItemsFromFile File="$(_CoreMLModel_BundleResourceCache)" Condition="Exists('$(_CoreMLModel_BundleResourceCache)')">
<Output TaskParameter="Items" ItemName="_BundleResourceWithLogicalName" />
@ -773,14 +828,14 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<PropertyGroup>
<_CoreCompileCoreMLModelsDependsOn>
$(_CoreCompileCoreMLModelsDependsOn);
_DetectAppManifest;
_ReadAppManifest;
_DetectSdkLocations;
_BeforeCompileCoreMLModels;
</_CoreCompileCoreMLModelsDependsOn>
</PropertyGroup>
<Target Name="_CoreCompileCoreMLModels"
Inputs="@(CoreMLModel);$(_AppManifest)"
Inputs="@(CoreMLModel);$(_TemporaryAppManifest)"
Outputs="$(_CoreMLModel_PartialAppManifestCache);$(_CoreMLModel_BundleResourceCache)"
DependsOnTargets="$(_CoreCompileCoreMLModelsDependsOn)">
@ -796,7 +851,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
ResourcePrefix="$(_ResourcePrefix)"
SdkDevPath="$(_SdkDevPath)">
<Output TaskParameter="BundleResources" ItemName="_BundleResourceWithLogicalName" />
<Output TaskParameter="PartialAppManifests" ItemName="PartialAppManifest" />
<Output TaskParameter="PartialAppManifests" ItemName="_PostCompilePartialAppManifest" />
<!-- Local items to be persisted to items files -->
<Output TaskParameter="PartialAppManifests" ItemName="_CoreMLModel_PartialAppManifest" />
@ -804,7 +859,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</CoreMLCompiler>
<!-- Cache the generated outputs items for incremental build support -->
<WriteItemsToFile Items="@(_CoreMLModel_PartialAppManifest)" ItemName="PartialAppManifest" File="$(_CoreMLModel_PartialAppManifestCache)" Overwrite="true" IncludeMetadata="true" />
<WriteItemsToFile Items="@(_CoreMLModel_PartialAppManifest)" ItemName="_PostCompilePartialAppManifest" File="$(_CoreMLModel_PartialAppManifestCache)" Overwrite="true" IncludeMetadata="true" />
<WriteItemsToFile Items="@(_CoreMLModel_BundleResources)" ItemName="_BundleResourceWithLogicalName" File="$(_CoreMLModel_BundleResourceCache)" Overwrite="true" IncludeMetadata="true" />
<ItemGroup>
<FileWrites Include="$(_CoreMLModel_PartialAppManifestCache);$(_CoreMLModel_BundleResourceCache)" />
@ -899,6 +954,9 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<Error Condition="'$(_AppManifest)' == '' And '$(_CanOutputAppBundle)' == 'true'" Text="Info.plist not found."/>
<PropertyGroup>
<_AppBundleManifest>$(_AppBundlePath)$(_AppBundleManifestRelativePath)Info.plist</_AppBundleManifest>
<!-- We should only write out a compiled app manifest if we're creating an app bundle -->
<_CreateAppManifest>$(_CanOutputAppBundle)</_CreateAppManifest>
<_TemporaryAppManifest>$(DeviceSpecificIntermediateOutputPath)AppManifest.plist</_TemporaryAppManifest>
</PropertyGroup>
</Target>
@ -986,10 +1044,10 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<PropertyGroup>
<_DetectSigningIdentityDependsOn>
$(_DetectSigningIdentityDependsOn);
_DetectAppManifest;
_DetectSdkLocations;
_ComputeTargetFrameworkMoniker;
_GenerateBundleName;
_ReadAppManifest;
</_DetectSigningIdentityDependsOn>
</PropertyGroup>
@ -997,10 +1055,8 @@ Copyright (C) 2018 Microsoft. All rights reserved.
<DetectSigningIdentity
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
ApplicationId="$(ApplicationId)"
AppBundleName="$(_AppBundleName)"
AppManifest="$(_AppManifest)"
GenerateApplicationManifest="$(GenerateApplicationManifest)"
BundleIdentifier="$(_BundleIdentifier)"
Keychain="$(CodesignKeychain)"
RequireCodeSigning="$(_RequireCodeSigning)"
RequireProvisioningProfile="$(_RequireProvisioningProfile)"
@ -1013,7 +1069,6 @@ Copyright (C) 2018 Microsoft. All rights reserved.
>
<Output TaskParameter="DetectedAppId" PropertyName="_AppIdentifier" />
<Output TaskParameter="DetectedBundleId" PropertyName="_BundleIdentifier" />
<Output TaskParameter="DetectedCodeSigningKey" PropertyName="_CodeSigningKey" />
<Output TaskParameter="DetectedCodesignAllocate" PropertyName="_CodesignAllocate" />
<Output TaskParameter="DetectedDistributionType" PropertyName="_DistributionType" />
@ -1429,15 +1484,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
/>
</Target>
<Target Name="_GetNativeExecutableName" DependsOnTargets="_DetectAppManifest;_GenerateBundleName;_CompileAppManifest">
<GetNativeExecutableName
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
AppManifest="$(_AppBundleManifest)"
>
<Output TaskParameter="ExecutableName" PropertyName="_ExecutableName" />
</GetNativeExecutableName>
<Target Name="_GetNativeExecutableName" DependsOnTargets="_DetectAppManifest;_GenerateBundleName;_ReadAppManifest">
<PropertyGroup>
<_NativeExecutable Condition="'$(_PlatformName)' == 'iOS' Or '$(_PlatformName)' == 'tvOS' Or '$(_PlatformName)' == 'watchOS'">$(_AppBundlePath)$(_ExecutableName)</_NativeExecutable>
<_NativeExecutable Condition="'$(_PlatformName)' == 'macOS' Or '$(_PlatformName)' == 'MacCatalyst'">$(_AppBundlePath)Contents\MacOS\$(_ExecutableName)</_NativeExecutable>
@ -1698,7 +1745,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</ItemGroup>
</Target>
<Target Name="_CompileProductDefinition" Condition="$(CreatePackage)" DependsOnTargets="_CompileAppManifest;_ComputeTargetArchitectures">
<Target Name="_CompileProductDefinition" Condition="$(CreatePackage)" DependsOnTargets="_WriteAppManifest;_ComputeTargetArchitectures">
<CompileProductDefinition
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
@ -1711,7 +1758,7 @@ Copyright (C) 2018 Microsoft. All rights reserved.
</CompileProductDefinition>
</Target>
<Target Name="_CreateInstaller" Condition="$(CreatePackage)" DependsOnTargets="Codesign;_CompileProductDefinition;_CompileAppManifest">
<Target Name="_CreateInstaller" Condition="$(CreatePackage)" DependsOnTargets="Codesign;_CompileProductDefinition;_WriteAppManifest">
<CreateInstallerPackage
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"

Просмотреть файл

@ -143,10 +143,6 @@ namespace Xamarin.iOS.Tasks
SetAppTransportSecurity (plist);
}
// Remove any Xamarin Studio specific keys
plist.Remove (ManifestKeys.XSLaunchImageAssets);
plist.Remove (ManifestKeys.XSAppIconAssets);
SetRequiredArchitectures (plist);
if (IsIOS)

Просмотреть файл

@ -108,7 +108,6 @@ Copyright (C) 2013-2016 Xamarin. All rights reserved.
<PropertyGroup Condition="'$(UsingAppleNETSdk)' != 'true'">
<CreateAppBundleDependsOn>
$(CreateAppBundleDependsOn);
_DetectAppManifest;
_DetectSigningIdentity;
_CopyResourcesToBundle;
_CreateAssetPackManifest;
@ -118,7 +117,7 @@ Copyright (C) 2013-2016 Xamarin. All rights reserved.
_CompileCoreMLModels;
_PrepareResourceRules;
_CompileEntitlements;
_CompileAppManifest;
_WriteAppManifest;
_GetNativeExecutableName;
_ParseBundlerArguments;
_CompileToNative;
@ -227,9 +226,7 @@ Copyright (C) 2013-2016 Xamarin. All rights reserved.
<_CompileITunesMetadataDependsOn>
$(_CompileITunesMetadataDependsOn);
_DetectSdkLocations;
_DetectAppManifest;
_GenerateBundleName;
_CompileAppManifest;
_ReadAppManifest;
</_CompileITunesMetadataDependsOn>
</PropertyGroup>
@ -274,12 +271,11 @@ Copyright (C) 2013-2016 Xamarin. All rights reserved.
<_CompileToNativeDependsOn>
_ComputeLinkMode;
_ComputeTargetFrameworkMoniker;
_DetectAppManifest;
_DetectSdkLocations;
_GenerateBundleName;
_DetectSigningIdentity;
_CompileEntitlements;
_CompileAppManifest;
_WriteAppManifest;
_ResolveAppExtensionReferences;
_ExtendAppExtensionReferences;
_GetNativeExecutableName;
@ -484,7 +480,6 @@ Copyright (C) 2013-2016 Xamarin. All rights reserved.
<PropertyGroup>
<_CompileTextureAtlasesDependsOn>
$(_CompileTextureAtlasesDependsOn);
_DetectAppManifest;
_DetectSdkLocations;
_BeforeCompileTextureAtlases;
_ReadCoreCompileTextureAtlases;

Просмотреть файл

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Xamarin.MacDev.Tasks;
@ -20,7 +21,7 @@ namespace Xamarin.iOS.Tasks
public bool ShouldCreateOutputFile (ITaskItem item) => true;
public IEnumerable<ITaskItem> GetAdditionalItemsToBeCopied () => new ITaskItem [] { new TaskItem (AppManifest) };
public IEnumerable<ITaskItem> GetAdditionalItemsToBeCopied () => Enumerable.Empty<ITaskItem> ();
public void Cancel ()
{

Просмотреть файл

@ -34,17 +34,17 @@ namespace Xamarin.iOS.Tasks
{
Task = CreateTask<CompileAppManifest> ();
Task.ApplicationId = identifier;
Task.AppBundleName = appBundleName;
Task.CompiledAppManifest = new TaskItem (Path.Combine (Cache.CreateTemporaryDirectory (), "AppBundlePath", "Info.plist"));
Task.AssemblyName = assemblyName;
Task.AppManifest = CreateTempFile ("foo.plist");
Task.BundleIdentifier = bundleIdentifier;
Task.MinimumOSVersion = string.Empty;
Task.SdkPlatform = "iPhoneSimulator";
Task.SdkVersion = "10.0";
Plist = new PDictionary ();
Plist ["CFBundleDisplayName"] = displayName;
Plist ["CFBundleIdentifier"] = identifier;
Plist ["CFBundleIdentifier"] = bundleIdentifier;
Plist.Save (Task.AppManifest);
}
@ -63,7 +63,8 @@ namespace Xamarin.iOS.Tasks
public void PlistMissing ()
{
File.Delete (Task.AppManifest);
Assert.IsFalse (Task.Execute (), "#1");
Assert.IsTrue (Task.Execute (), "#1");
Assert.That (Task.CompiledAppManifest.ItemSpec, Does.Exist, "#2");
}
[Test]

Просмотреть файл

@ -565,7 +565,7 @@ namespace Xamarin.iOS.Tasks
public void DetectAppManifest_ExecutableProject ()
{
RunTarget (MonoTouchProject, TargetName.DetectAppManifest);
Assert.IsFalse (string.IsNullOrEmpty(MonoTouchProjectInstance.GetPropertyValue ("_AppManifest")), "#1");
Assert.That (MonoTouchProjectInstance.GetPropertyValue ("_AppManifest"), Is.Not.Null.And.Not.Empty, "#1");
}
[Test]

Просмотреть файл

@ -55,5 +55,25 @@ namespace Xamarin.Utils {
}
} while (true);
}
// Returns true if the target file was updated, false if the target file was already up-to-date.
public static bool UpdateFile (string targetFile, Action<string> createOutput)
{
var tmpFile = Path.GetTempFileName ();
try {
createOutput (tmpFile);
if (File.Exists (targetFile) && FileUtils.CompareFiles (tmpFile, targetFile)) {
// File is up-to-date
return false;
} else {
Directory.CreateDirectory (Path.GetDirectoryName (targetFile));
File.Copy (tmpFile, targetFile, true);
return true;
}
} finally {
File.Delete (tmpFile);
}
}
}
}