Skip to content

Commit

Permalink
[Xamarin.Android.Tools.AndroidSdk] Add support for cmdline-tools (#83)
Browse files Browse the repository at this point in the history
Context: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1109288
Context: https://dl.google.com/android/repository/repository2-1.xml

Google has deprecated the `tools` Android SDK package, replacing it
with the `cmdline-tools` package, which introduces a version
directory component.

Old and busted:

	$(AndroidSdkDirectory)/tools/bin/sdkmanager

New hotness:

	$(AndroidSdkDirectory)/cmdline-tools/latest/bin/sdkmanager

Of particular interest is that `latest` is a *literal value*.  There
is also a `cmdline-tools;1.0` package which creates a
`cmdline-tools/1.0` directory.

Add a new `AndroidSdkInfo.GetCommandLineToolsPaths()` method which
returns the "command-line tools paths", ordered by version and (non-)
obsolescence.  For example, given the directory structure:

  * `$(AndroidSdkDirectory)/tools/bin/sdkmanager`
  * `$(AndroidSdkDirectory)/cmdline-tools/1.0/bin/sdkmanager`
  * `$(AndroidSdkDirectory)/cmdline-tools/latest/bin/sdkmanager`

Then `AndroidSdkInfo.GetCommandLineToolsPaths()` will return, 
in this order:

  * `$(AndroidSdkDirectory)/cmdline-tools/latest`
  * `$(AndroidSdkDirectory)/cmdline-tools/1.0`
  * `$(AndroidSdkDirectory)/tools`

The `latest` version is always preferred, if present, followed by any
actually versioned cmdline-tools directories, followed by the `tools`
directory, if it exists.

Note that "prefixes" are returned.  All utilities are within a nested
`bin` directory, so if you want e.g. the latest `sdkmanager` util,
you would want to do:

	var info = new AndroidSdkInfo (path);
	var latestSdkManager = Path.Combine (
	        info.GetCommandLineToolsPaths ().First (),
	        "bin",
	        "sdkmanager");

Finally, remove some unnecessary members from `AndroidSdkBase` which
were never used -- and thus are "noise" -- and don't make sense in
the new `cmdline-tools` world, as the cmdline-tools package doesn't
contain them…
  • Loading branch information
DmitriyKirakosyan committed Apr 30, 2020
1 parent f473ff9 commit f5fcb9f
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 25 deletions.
50 changes: 45 additions & 5 deletions src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,7 @@ public IEnumerable<string> GetBuildToolsPaths ()
foreach (var d in preview)
yield return d;

var sorted = from p in Directory.EnumerateDirectories (buildTools)
let version = TryParseVersion (Path.GetFileName (p))
where version != null
orderby version descending
select p;
var sorted = SortedSubdirectoriesByVersion (buildTools);

foreach (var d in sorted)
yield return d;
Expand All @@ -66,6 +62,15 @@ orderby version descending
yield return ptPath;
}

static IEnumerable<string> SortedSubdirectoriesByVersion (string dir)
{
return from p in Directory.EnumerateDirectories (dir)
let version = TryParseVersion (Path.GetFileName (p))
where version != null
orderby version descending
select p;
}

static Version TryParseVersion (string v)
{
Version version;
Expand Down Expand Up @@ -185,5 +190,40 @@ public static void DetectAndSetPreferredJavaSdkPathToLatest (Action<TraceLevel,
var sdk = CreateSdk (logger);
sdk.SetPreferredJavaSdkPath (latestJdk.HomePath);
}

public string TryGetCommandLineToolsPath ()
{
return GetCommandLineToolsPaths ("latest").FirstOrDefault ();
}

public IEnumerable<string> GetCommandLineToolsPaths (string preferredCommandLineToolsVersion)
{
if (!string.IsNullOrEmpty (preferredCommandLineToolsVersion)) {
var preferredDir = Path.Combine (AndroidSdkPath, "cmdline-tools", preferredCommandLineToolsVersion);
if (Directory.Exists (preferredDir))
return new[] { preferredDir }.Concat (GetCommandLineToolsPaths ().Where (p => p != preferredDir));
}
return GetCommandLineToolsPaths ();
}

public IEnumerable<string> GetCommandLineToolsPaths ()
{
var cmdlineToolsDir = Path.Combine (AndroidSdkPath, "cmdline-tools");
if (Directory.Exists (cmdlineToolsDir)) {
var latestDir = Path.Combine (cmdlineToolsDir, "latest");
if (Directory.Exists (latestDir))
yield return latestDir;
foreach (var d in SortedSubdirectoriesByVersion (cmdlineToolsDir)) {
var version = Path.GetFileName (d);
if (version == "latest")
continue;
yield return d;
}
}
var toolsDir = Path.Combine (AndroidSdkPath, "tools");
if (Directory.Exists (toolsDir)) {
yield return toolsDir;
}
}
}
}
12 changes: 0 additions & 12 deletions src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,10 @@ public AndroidSdkBase (Action<TraceLevel, string> logger)
public string AndroidNdkPath { get; private set; }
public string JavaSdkPath { get; private set; }
public string JavaBinPath { get; private set; }
public string AndroidToolsPath { get; private set; }
public string AndroidPlatformToolsPath { get; private set; }
public string AndroidToolsPathShort { get; private set; }
public string AndroidPlatformToolsPathShort { get; private set; }

public virtual string Adb { get; protected set; } = "adb";
public virtual string Android { get; protected set; } = "android";
public virtual string Emulator { get; protected set; } = "emulator";
public virtual string Monitor { get; protected set; } = "monitor";
public virtual string ZipAlign { get; protected set; } = "zipalign";
public virtual string JarSigner { get; protected set; } = "jarsigner";
public virtual string KeyTool { get; protected set; } = "keytool";
Expand Down Expand Up @@ -76,13 +71,9 @@ public virtual void Initialize (string androidSdkPath = null, string androidNdkP
}

if (!string.IsNullOrEmpty (AndroidSdkPath)) {
AndroidToolsPath = Path.Combine (AndroidSdkPath, "tools");
AndroidToolsPathShort = GetShortFormPath (AndroidToolsPath);
AndroidPlatformToolsPath = Path.Combine (AndroidSdkPath, "platform-tools");
AndroidPlatformToolsPathShort = GetShortFormPath (AndroidPlatformToolsPath);
} else {
AndroidToolsPath = null;
AndroidToolsPathShort = null;
AndroidPlatformToolsPath = null;
AndroidPlatformToolsPathShort = null;
}
Expand All @@ -98,9 +89,6 @@ public virtual void Initialize (string androidSdkPath = null, string androidNdkP
// we need to look for extensions other than the default .exe|.bat
// google have a habbit of changing them.
Adb = GetExecutablePath (AndroidPlatformToolsPath, Adb);
Android = GetExecutablePath (AndroidToolsPath, Android);
Emulator = GetExecutablePath (AndroidToolsPath, Emulator);
Monitor = GetExecutablePath (AndroidToolsPath, Monitor);
NdkStack = GetExecutablePath (AndroidNdkPath, NdkStack);
}

Expand Down
104 changes: 96 additions & 8 deletions tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,74 @@ public void JdkDirectory_JavaHome ()
}
}

[Test]
public void Sdk_GetCommandLineToolsPaths ()
{
CreateSdks(out string root, out string jdk, out string ndk, out string sdk);

var cmdlineTools = Path.Combine (sdk, "cmdline-tools");
var latestToolsVersion = "latest";
var toolsVersion = "2.1";
var higherToolsVersion = "11.2";

void recreateCmdlineToolsDirectory () {
Directory.Delete (cmdlineTools, recursive: true);
Directory.CreateDirectory (cmdlineTools);
}

try {
var info = new AndroidSdkInfo (androidSdkPath: sdk);

// Test cmdline-tools path
recreateCmdlineToolsDirectory();
CreateFauxAndroidSdkToolsDirectory (sdk, createToolsDir: true, toolsVersion: toolsVersion, createOldToolsDir: false);
var toolsPaths = info.GetCommandLineToolsPaths ();

Assert.AreEqual (toolsPaths.Count (), 1, "Incorrect number of elements");
Assert.AreEqual (toolsPaths.First (), Path.Combine (sdk, "cmdline-tools", toolsVersion), "Incorrect command line tools path");

// Test that cmdline-tools is preferred over tools
recreateCmdlineToolsDirectory();
CreateFauxAndroidSdkToolsDirectory (sdk, createToolsDir: true, toolsVersion: latestToolsVersion, createOldToolsDir: true);
toolsPaths = info.GetCommandLineToolsPaths ();

Assert.AreEqual (toolsPaths.Count (), 2, "Incorrect number of elements");
Assert.AreEqual (toolsPaths.First (), Path.Combine (sdk, "cmdline-tools", latestToolsVersion), "Incorrect command line tools path");
Assert.AreEqual (toolsPaths.Last (), Path.Combine (sdk, "tools"), "Incorrect tools path");

// Test sorting
recreateCmdlineToolsDirectory ();
CreateFauxAndroidSdkToolsDirectory (sdk, createToolsDir: true, toolsVersion: latestToolsVersion, createOldToolsDir: false);
CreateFauxAndroidSdkToolsDirectory (sdk, createToolsDir: true, toolsVersion: toolsVersion, createOldToolsDir: false);
CreateFauxAndroidSdkToolsDirectory (sdk, createToolsDir: true, toolsVersion: higherToolsVersion, createOldToolsDir: true);
toolsPaths = info.GetCommandLineToolsPaths ();

var toolsPathsList = toolsPaths.ToList ();
Assert.AreEqual (toolsPaths.Count (), 4, "Incorrect number of elements");
bool isOrderCorrect = toolsPathsList [0].Equals (Path.Combine (sdk, "cmdline-tools", latestToolsVersion), StringComparison.Ordinal)
&& toolsPathsList [1].Equals (Path.Combine (sdk, "cmdline-tools", higherToolsVersion), StringComparison.Ordinal)
&& toolsPathsList [2].Equals (Path.Combine (sdk, "cmdline-tools", toolsVersion), StringComparison.Ordinal)
&& toolsPathsList [3].Equals (Path.Combine (sdk, "tools"), StringComparison.Ordinal);

Assert.IsTrue (isOrderCorrect, "Tools order is not descending");
} finally {
Directory.Delete (root, recursive: true);
}
}

static bool IsWindows => OS.IsWindows;

static void CreateSdks (out string root, out string jdk, out string ndk, out string sdk)
static string CreateRoot ()
{
root = Path.GetTempFileName ();
var root = Path.GetTempFileName ();
File.Delete (root);
Directory.CreateDirectory (root);
return root;
}

static void CreateSdks (out string root, out string jdk, out string ndk, out string sdk)
{
root = CreateRoot ();

ndk = Path.Combine (root, "ndk");
sdk = Path.Combine (root, "sdk");
Expand All @@ -185,25 +246,52 @@ static void CreateSdks (out string root, out string jdk, out string ndk, out str
CreateFauxJavaSdkDirectory (jdk, "1.8.0", out var _, out var _);
}

static void CreateFauxAndroidSdkDirectory (string androidSdkDirectory, string buildToolsVersion, ApiInfo [] apiLevels = null)
static void CreateFauxAndroidSdkToolsDirectory (string androidSdkDirectory, bool createToolsDir, string toolsVersion, bool createOldToolsDir)
{
if (createToolsDir) {
string androidSdkToolsPath = Path.Combine (androidSdkDirectory, "cmdline-tools", toolsVersion ?? "1.0");
string androidSdkToolsBinPath = Path.Combine (androidSdkToolsPath, "bin");

Directory.CreateDirectory (androidSdkToolsPath);
Directory.CreateDirectory (androidSdkToolsBinPath);

File.WriteAllText (Path.Combine (androidSdkToolsBinPath, IsWindows ? "lint.bat" : "lint"), "");
}

if (createOldToolsDir) {
string androidSdkToolsPath = Path.Combine (androidSdkDirectory, "tools");
string androidSdkToolsBinPath = Path.Combine (androidSdkToolsPath, "bin");

Directory.CreateDirectory (androidSdkToolsPath);
Directory.CreateDirectory (androidSdkToolsBinPath);

File.WriteAllText (Path.Combine (androidSdkToolsBinPath, IsWindows ? "lint.bat" : "lint"), "");
}

}

static void CreateFauxAndroidSdkDirectory (
string androidSdkDirectory,
string buildToolsVersion,
bool createToolsDir = true,
string toolsVersion = null,
bool createOldToolsDir = false,
ApiInfo[] apiLevels = null)
{
var androidSdkToolsPath = Path.Combine (androidSdkDirectory, "tools");
var androidSdkBinPath = Path.Combine (androidSdkToolsPath, "bin");
CreateFauxAndroidSdkToolsDirectory (androidSdkDirectory, createToolsDir, toolsVersion, createOldToolsDir);

var androidSdkPlatformToolsPath = Path.Combine (androidSdkDirectory, "platform-tools");
var androidSdkPlatformsPath = Path.Combine (androidSdkDirectory, "platforms");
var androidSdkBuildToolsPath = Path.Combine (androidSdkDirectory, "build-tools", buildToolsVersion);

Directory.CreateDirectory (androidSdkDirectory);
Directory.CreateDirectory (androidSdkToolsPath);
Directory.CreateDirectory (androidSdkBinPath);
Directory.CreateDirectory (androidSdkPlatformToolsPath);
Directory.CreateDirectory (androidSdkPlatformsPath);
Directory.CreateDirectory (androidSdkBuildToolsPath);

File.WriteAllText (Path.Combine (androidSdkPlatformToolsPath, IsWindows ? "adb.exe" : "adb"), "");
File.WriteAllText (Path.Combine (androidSdkBuildToolsPath, IsWindows ? "zipalign.exe" : "zipalign"), "");
File.WriteAllText (Path.Combine (androidSdkBuildToolsPath, IsWindows ? "aapt.exe" : "aapt"), "");
File.WriteAllText (Path.Combine (androidSdkToolsPath, IsWindows ? "lint.bat" : "lint"), "");

List<ApiInfo> defaults = new List<ApiInfo> ();
for (int i = 10; i < 26; i++) {
Expand Down

0 comments on commit f5fcb9f

Please sign in to comment.