diff --git a/Documentation/guides/OneDotNetSingleProject.md b/Documentation/guides/OneDotNetSingleProject.md
index a534a877bd0..babdac788af 100644
--- a/Documentation/guides/OneDotNetSingleProject.md
+++ b/Documentation/guides/OneDotNetSingleProject.md
@@ -19,25 +19,27 @@ Xamarin.Android and Xamarin.iOS/Mac SDKs:
* `$(ApplicationId)` maps to `/manifest/@package` and
`CFBundleIdentifier`
-* `$(ApplicationVersion)` maps to `android:versionName` or
- `CFBundleVersion`. This is a version string that must be incremented
- for each iOS App Store or TestFlight submission.
-* `$(AndroidVersionCode)` maps to `android:versionCode` (_Android
- only)_. This is unfortunately an integer and must be incremented for
- each Google Play submission.
-* `$(AppleShortVersion)` maps to `CFBundleShortVersionString` (_iOS
- only)_. This can default to `$(ApplicationVersion)` when blank.
+* `$(ApplicationVersion)` maps to `android:versionCode` or
+ [`CFBundleVersion`][CFBundleVersion]. This is required to be an integer on Android and
+ less than 10000 on iOS. This value must be incremented for each
+ Google Play or App Store / TestFlight submission.
+* `$(ApplicationDisplayVersion)` maps to `android:versionName` or
+ [`CFBundleShortVersionString`][CFBundleShortVersionString]. This can
+ default to `$(ApplicationVersion)` when blank.
* `$(ApplicationTitle)` maps to `/application/@android:title` or
`CFBundleDisplayName`
+[CFBundleVersion]: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102364
+[CFBundleShortVersionString]: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-111349
+
The final value that is generated in the `Info.plist` or
`AndroidManifest.xml` can be overridden at different times. The final
source of truth is determined in order of:
-1. `Info.plist` or `AndroidManifest.xml` in the iOS/Android head project.
-2. iOS/Android head `.csproj` defines the MSBuild properties
-3. _(To be implemented in MAUI/Forms)_ set in a shared `.csproj`.
-4. The properties set by MSBuild via other means such as
+1. `Info.plist` or `AndroidManifest.xml` in the iOS/Android project.
+2. iOS/Android `.csproj` defines the MSBuild properties. This could
+ also be done in a .NET MAUI "Single Project".
+3. The properties set by MSBuild via other means such as
`Directory.Build.props`, etc.
Even if we did not complete the goal of complete removal of
@@ -57,7 +59,7 @@ property to disable the behavior.
In most cases, developers would only use `$(GenerateApplicationManifest)`
if they want to try the new features in "legacy" Xamarin.
-## AssemblyVersion and FileVersion
+## Version, AssemblyVersion, FileVersion, and InformationalVersion
Since we are adding *more* version properties, we should consider
adding defaults to consolidate the assembly-level attributes when
@@ -67,16 +69,22 @@ The full list of defaults might be something like:
```xml
- 1.0
-
- 1
-
- $(ApplicationVersion)
- $(ApplicationVersion)
- $(ApplicationVersion)
+ 1
+ $(ApplicationDisplayVersion)
+ $(Version)
```
+The dotnet/sdk defaults `$(Version)` to 1.0.0 and uses it to set:
+
+* `$(AssemblyVersion)`
+* `$(FileVersion)`
+* `$(InformationalVersion)`
+
+If we expect users to set `$(ApplicationVersion)` and
+`$(ApplicationDisplayVersion)` in mobile apps, we can use the value of
+`$(ApplicationDisplayVersion)` for `$(Version)` as well.
+
## Android Template
The default Android project template would include:
@@ -88,8 +96,8 @@ The default Android project template would include:
net6.0-android
@string/application_title
com.companyname.myapp
- 1.0
- 1
+ 1
+ 1.0
@@ -119,14 +127,12 @@ The default iOS project template would include:
net6.0-ios
MyApp
com.companyname.myapp
- 1.0
+ 1
+ 1.0
```
-`$(AppleShortVersion)` can default to `$(ApplicationVersion)` when
-blank.
-
Removed from `Info.plist` in the project template:
* `CFBundleDisplayName`
@@ -139,45 +145,26 @@ MSBuild properties.
## Example
-You could setup a cross-platform solution in .NET 6 with:
+The .NET MAUI project template (`dotnet new maui`):
-* `Hello/Hello.csproj` - `net6.0` shared code
-* `HelloAndroid/HelloAndroid.csproj` - `net6.0-android`
-* `HelloiOS/HelloiOS.csproj` - `net6.0-ios`
-* `Hello.sln`
-* `Directory.Build.props`
+* `HelloMaui/HelloMaui.csproj` - multi-targeted for `net6.0-android`,
+ `net6.0-ios`, `net6.0-maccatalyst`, etc.
-Where `Directory.Build.props` can be setup for both platforms at once
-with:
+Where the versions can be setup for both platforms at once with:
```xml
Hello!
com.companyname.hello
- 1.0.0
- 1
+ 1
+ 1.0
```
In this project, a developer would increment `$(ApplicationVersion)`
-and `$(AndroidVersionCode)` for each public release.
-
-For our long-term vision, we could one day have a single project that
-multi-targets:
-
-```xml
-
-
- net6.0-android;net6.0-ios
- Hello!
- com.companyname.hello
- 1.0.0
- 1
-
-
-```
+and `$(ApplicationDisplayVersion)` for each public release.
## Localization
@@ -203,8 +190,7 @@ and Android. This is a consideration for the future.
In future iterations, we can consider additional MSBuild properties
beyond `$(ApplicationTitle)`, `$(ApplicationId)`,
-`$(ApplicationVersion)`, `$(AndroidVersionCode)`, and
-`$(AppleShortVersion)`.
+`$(ApplicationVersion)`, and `$(ApplicationDisplayVersion)`.
This is a list of additional properties that cover most of the
property pages in Visual Studio:
diff --git a/src/Microsoft.Android.Templates/android/AndroidApp1.csproj b/src/Microsoft.Android.Templates/android/AndroidApp1.csproj
index 906e1314de1..16f9ea6f3e5 100644
--- a/src/Microsoft.Android.Templates/android/AndroidApp1.csproj
+++ b/src/Microsoft.Android.Templates/android/AndroidApp1.csproj
@@ -6,5 +6,8 @@
Exe
enable
enable
+ com.companyname.AndroidApp1
+ 1
+ 1.0
\ No newline at end of file
diff --git a/src/Microsoft.Android.Templates/android/AndroidManifest.xml b/src/Microsoft.Android.Templates/android/AndroidManifest.xml
index c669bdfaa58..d9115795753 100644
--- a/src/Microsoft.Android.Templates/android/AndroidManifest.xml
+++ b/src/Microsoft.Android.Templates/android/AndroidManifest.xml
@@ -1,8 +1,5 @@
-
+
\ No newline at end of file
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets
index ffef701783d..ccacac7e0fb 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets
@@ -101,10 +101,10 @@
True
- 1.0
- 1
- $(ApplicationVersion)
- $(ApplicationVersion)
+
+ 1
+ $(ApplicationDisplayVersion)
+ $(Version)
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/SingleProjectTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/SingleProjectTest.cs
index 294278bb59f..52bcb932d8b 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/SingleProjectTest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/SingleProjectTest.cs
@@ -1,7 +1,9 @@
+using System;
using System.IO;
+using System.Linq;
using System.Xml.Linq;
+using Mono.Cecil;
using NUnit.Framework;
-using Xamarin.Android.Tasks;
using Xamarin.Android.Tools;
using Xamarin.ProjectTools;
@@ -11,13 +13,35 @@ namespace Xamarin.Android.Build.Tests
[Parallelizable (ParallelScope.Children)]
public partial class SingleProjectTest : BaseTest
{
+ static readonly object [] AndroidManifestPropertiesSource = new object [] {
+ new object [] {
+ /* versionName */ "2.1",
+ /* versionCode */ "42",
+ /* errorMessage */ "",
+ },
+ new object [] {
+ /* versionName */ "1.0.0",
+ /* versionCode */ "1.0.0",
+ /* errorMessage */ "XA0003",
+ },
+ new object [] {
+ /* versionName */ "3.1.3a1",
+ /* versionCode */ "42",
+ /* errorMessage */ "",
+ },
+ new object [] {
+ /* versionName */ "6.0-preview.7",
+ /* versionCode */ "42",
+ /* errorMessage */ "",
+ },
+ };
+
[Test]
- public void AndroidManifestProperties ()
+ [TestCaseSource (nameof (AndroidManifestPropertiesSource))]
+ public void AndroidManifestProperties (string versionName, string versionCode, string errorMessage)
{
var packageName = "com.xamarin.singleproject";
var applicationLabel = "My Sweet App";
- var versionName = "2.1";
- var versionCode = "42";
var proj = new XamarinAndroidApplicationProject ();
proj.AndroidManifest = proj.AndroidManifest
.Replace ("package=\"${PACKAGENAME}\"", "")
@@ -29,12 +53,18 @@ public void AndroidManifestProperties ()
}
proj.SetProperty ("ApplicationId", packageName);
proj.SetProperty ("ApplicationTitle", applicationLabel);
- proj.SetProperty ("ApplicationVersion", versionName);
- proj.SetProperty ("AndroidVersionCode", versionCode);
+ proj.SetProperty ("ApplicationVersion", versionCode);
+ proj.SetProperty ("ApplicationDisplayVersion", versionName);
using (var b = CreateApkBuilder ()) {
- Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
+ if (!string.IsNullOrEmpty (errorMessage)) {
+ b.ThrowOnBuildFailure = false;
+ Assert.IsFalse (b.Build (proj), "Build should have failed.");
+ StringAssertEx.Contains (errorMessage, b.LastBuildOutput, $"Build should fail with message '{errorMessage}'");
+ return;
+ }
+ Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
var manifest = b.Output.GetIntermediaryPath ("android/AndroidManifest.xml");
FileAssert.Exists (manifest);
@@ -48,6 +78,34 @@ public void AndroidManifestProperties ()
var apk = b.Output.GetIntermediaryPath ($"android/bin/{packageName}.apk");
FileAssert.Exists (apk);
+
+ // NOTE: the $(Version) setting is only implemented in .NET 6
+ if (!Builder.UseDotNet)
+ return;
+ // If not valid version, skip
+ if (!Version.TryParse (versionName, out _))
+ return;
+
+ int index = versionName.IndexOf ('-');
+ var versionNumber = index == -1 ?
+ $"{versionName}.0.0" :
+ $"{versionName.Substring (0, index)}.0.0";
+ var assemblyPath = b.Output.GetIntermediaryPath ($"android/assets/{proj.ProjectName}.dll");
+ FileAssert.Exists (assemblyPath);
+ using var assembly = AssemblyDefinition.ReadAssembly (assemblyPath);
+
+ // System.Reflection.AssemblyVersion
+ Assert.AreEqual (versionNumber, assembly.Name.Version.ToString ());
+
+ // System.Reflection.AssemblyFileVersion
+ var assemblyInfoVersion = assembly.CustomAttributes.FirstOrDefault (a => a.AttributeType.FullName == "System.Reflection.AssemblyInformationalVersionAttribute");
+ Assert.IsNotNull (assemblyInfoVersion, "Should find AssemblyInformationalVersionAttribute!");
+ Assert.AreEqual (versionName, assemblyInfoVersion.ConstructorArguments [0].Value);
+
+ // System.Reflection.AssemblyInformationalVersion
+ var assemblyFileVersion = assembly.CustomAttributes.FirstOrDefault (a => a.AttributeType.FullName == "System.Reflection.AssemblyFileVersionAttribute");
+ Assert.IsNotNull (assemblyFileVersion, "Should find AssemblyFileVersionAttribute!");
+ Assert.AreEqual (versionNumber, assemblyFileVersion.ConstructorArguments [0].Value);
}
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index 61d9b1ef053..5566a3cb460 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -503,8 +503,8 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<_AndroidPackage>$(ApplicationId)
<_ApplicationLabel>$(ApplicationTitle)
- <_AndroidVersionName>$(ApplicationVersion)
- <_AndroidVersionCode Condition=" '$(AndroidCreatePackagePerAbi)' != 'true' ">$(AndroidVersionCode)
+ <_AndroidVersionName>$(ApplicationDisplayVersion)
+ <_AndroidVersionCode Condition=" '$(AndroidCreatePackagePerAbi)' != 'true' ">$(ApplicationVersion)