Xamarin-macios: [Feature Request] Add support for Attributes to build Info.plist

Created on 18 Apr 2019  Â·  11Comments  Â·  Source: xamarin/xamarin-macios

Description

In Xamarin Android we have a number of really helpful attributes that can generally replace the need for creating complex Android Manifests. Some examples would include the UsesPermissionAttribute, IntentFilterAttribute, ApplicationAttribute, ActivityAttribute, etc. It's about high time that this sort of attribute love made it's way to iOS where we could start decorating our code with attributes that the build targets could then use to generate the full Info.plist or even to update the Entitilements.plist.

Example Use Case

A common use case for this would be when using App Center Distribution. Assuming a full CI/CD pipeline here I might have my app split up 3 times in App Center for Dev, Stage and Production. This means for each environment I've got a unique App Secret for App Center. What's more is I might want to use Distribution for Dev and Stage but not Production. If we look at the App Center Distribution docs we'll see that we need to add a CFBundleURLSchemes in the CFBundleURLTypes:

<key>CFBundleURLTypes</key>
  <array>
      <dict>
          <key>CFBundleURLSchemes</key>
          <array>
              <string>appcenter-${APP_SECRET}</string>
          </array>
      </dict>
  </array>

As an attribute this might look something like:

[assembly: CFBundleUrlType(Schemes = new[] { "appcenter-{app_secret}" })]

Or really for best practices it would probably look more like:

[assembly: CFBundleUrlType(Schemes = new[] { Constants.AppCenterScheme })]

The benefit here is that we can rely on processes we're probably are already using to inject things like the App Center secret at build time rather than having to deal with the nightmare of trying to make sure it doesn't get checked into source control and then writing a bunch of special build scripts to go and do regex replacements of some token in the Info.plist.

Proposed Attributes

public enum BundleUrlTypeRole
{
    None,
    Editor,
    Viewer
}

public class CFBundleUrlTypeAttribute : Attribute
{
    public BundleUrlTypeRole Role { get; set; }
    public string Name { get; set; }
    public string[] Schemes { get; set; }
    public string Icon { get; set; }
}

public class UIApplicationAttribute : Attribute
{
    public string Name { get; set; } // AwesomeApp
    public string DisplayName { get; set; } // Awesome App
    public string Identifier { get; set; }  // com.contoso.awesomeapp
    public string MinimumOSVersion { get; set; } // 10.0
    public string Version { get; set; } // 1.0.0.1234
    public string ShortVersion { get; set; } // 1.0
    public string[] Fonts { get; set; } 
    public bool AutoLoadFonts { get; set; } // if true add any ttf/otf that is a BundleResource
}

public static class PrivacyUsage
{
    public const string Camera = nameof(Camera);
    public const string Contacts = nameof(Contacts);
    public const string Health = nameof(Health);
    public const string LocationAlways = nameof(LocationAlways);
    public const string Location = nameof(Location);
    public const string LocationWhenInUse = nameof(LocationWhenInUse);
    // etc...
}

public class PrivacyAttribute : Attribute
{
    public PrivatcyAttribute(string permission, string description) { }

    public string Permission { get; } // i.e. Camera
    public string Description { get; }  // We want to take fun photos of your dinner for Instagram
}

public enum ApsEnvironment
{
    Development,
    Production
}

public class PushNotificationAttribute : Attribute
{
    public PushNotificationAttribute(ApsEnvironment environment) { }
    public ApsEnvironment Environment { get; }
}

// Assumed to be enabled if the attribute exists
public class DataProtectionAttribute : Attribute
{
}

public class KeychainAttribute : Attribute
{
    public KeychainAttribute(string[] groups) { }
    public string[] Groups { get; }
}

public class AssociatedDomainsAttribute
 : Attribute
{
    public AssociatedDomainsAttribute
(string[] domains) { }
    public string[] Domains { get; }
}

public class AppGroupsAttribute : Attribute
{
    public AppGroupsAttribute (string[] domains) { }
    public string[] Domains { get; }
}

Putting it all together

To put this all together you might have something like the following to handle the above Use case in which you have an app with 3 environments Dev, Stage, & Production:

// Properties like the Version, Min OS Version would not be touched in the 
// Info.plist because they are not set here.
#if STAGE
[assembly: UIApplication(
  Name = "AwesomeAppStage",
  DisplayName = "Awesome App (Stage)",
  Identifier = "com.contoso.awesomeapp-stage",
  AutoLoadFonts = true
)]
#elif STORE
[assembly: UIApplication(
  Name = "AwesomeApp",
  DisplayName = "Awesome App",
  Identifier = "com.contoso.awesomeapp",
  AutoLoadFonts = true
)]
#else
[assembly: UIApplication(
  Name = "AwesomeAppDev",
  DisplayName = "Awesome App (Dev)",
  Identifier = "com.contoso.awesomeapp-dev",
  AutoLoadFonts = true
)]
#endif

#if STORE
[assembly: PushNotification(ApsEnvironment.Production)]
#else
[assembly: PushNotification(ApsEnvironment.Development)]
[assembly: CFBundleUrlType(Schemes = new[] { Constants.AppCenterScheme })]
#endif

[assembly: DataProtection]
[assembly: Keychain(new[] { "com.contoso.awesomeapp" })]

[assembly: Privacy(PrivacyUsage.Camera, "We need to scan a barcode")]
[assembly: Privacy(PrivacyUsage.Location, "We track your movements and report them to your boss")]

At build time there should be a build task that transforms both the Info.plist and Entitlements.plist based on these attributes so that you are in essence updating your plist from code and without the need for crazy build scripts.

enhancement iOS macOS

Most helpful comment

@rolfbjarne @spouliot Any update on this feature request?

All 11 comments

I love the idea :)

They (attributes) needs to come up with rules for what happens if

  • Info.plist already has values;
  • if more than one assembly has the attributes;

We can already limit usage to single use in assemblies - for a singe attribute. However something like

[assembly: Privacy(PrivacyUsage.Camera, "We need to scan a barcode")]
[assembly: Privacy(PrivacyUsage.Location, "We track your movements and report them to your boss")]

must be allowed so we also need a rule for

[assembly: Privacy(PrivacyUsage.Camera, "We need to scan a barcode")]
[assembly: Privacy(PrivacyUsage.Camera, "We need your picture too")]

Info.plist already has values;

I would think that in this case we want to overwrite the value in the Plist… Perhaps this could be surfaced with a build warning

if more than one assembly has the attributes;

I would think we would only scan the assembly of the project that contains the Info.plist/Entitlements.plist so you wouldn't have a multi-assembly issue there.

(paraphrased) Duplicate Privacy Attributes with the same PrivateUsage

I'd have to ask (because I honestly have never tried) what would happen if you tried to add the duplicate keys in the Info.plist? Is there a process currently that would catch this?

That said I would think we want to surface this in the build output as either an Error or Warning that specifies what the duplicated PrivacyUsage is and what the descriptions of each are.

@spouliot I've put together a POC repo. I'm still trying to work out where to inject the build task correctly. I'd welcome any feedback you may have there. Perhaps we can put this out as a POC and develop the feature there then bring it over?

Do we want to link away these attributes? They're not really needed after the build.

@rolfbjarne I would tend to agree... I can't see any harm in linking them away once the build task has run.

It would probably make most sense to put this logic in https://github.com/xamarin/xamarin-macios/blob/master/msbuild/Xamarin.iOS.Tasks.Core/Tasks/CompileAppManifestTaskBase.cs then. I'm not sure how to inject any custom logic there without modifying our build though.

@rolfbjarne @spouliot Any update on this feature request?

@jennyf19 Unfortunately no, there's no progress/news about this, we haven't had time to look at it.

Thanks for the update @rolfbjarne, we'd love to see this feature to make our samples easier to use. Hopefully it will get added to the backlog soon.

cc: @TiagoBrenck

@rolfbjarne Any update? thanks.

@jennyf19 I'm sorry to say we have no updates since last time, we've been busy with other tasks.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ormaa picture ormaa  Â·  3Comments

jzeferino picture jzeferino  Â·  3Comments

rolfbjarne picture rolfbjarne  Â·  4Comments

whitneyschmidt picture whitneyschmidt  Â·  3Comments

sharmashiv picture sharmashiv  Â·  4Comments