diff --git a/build-tools/xaprepare/xaprepare/Application/RegexProgramVersionParser.cs b/build-tools/xaprepare/xaprepare/Application/RegexProgramVersionParser.cs
index 90295cc8504..67dc9cdc971 100644
--- a/build-tools/xaprepare/xaprepare/Application/RegexProgramVersionParser.cs
+++ b/build-tools/xaprepare/xaprepare/Application/RegexProgramVersionParser.cs
@@ -12,78 +12,78 @@ namespace Xamarin.Android.Prepare
/// is set to any other value only that line is taken into consideration. This is
/// done to make processing less ambiguous and faster.
///
- class RegexProgramVersionParser : ProgramVersionParser
- {
- const string VersionGroupName = "Version";
- static readonly char[] LineSeparator = new [] { '\n' };
+ class RegexProgramVersionParser : ProgramVersionParser
+ {
+ public const string VersionGroupName = "Version";
+ public static readonly char[] LineSeparator = new [] { '\n' };
- Regex rx;
+ Regex rx;
- public RegexProgramVersionParser (string programName, string versionArguments, Regex regex, uint versionOutputLine = 0, Log? log = null)
- : base (programName, versionArguments, versionOutputLine, log)
- {
- if (regex == null)
- throw new ArgumentNullException (nameof (regex));
- rx = regex;
- }
+ public RegexProgramVersionParser (string programName, string versionArguments, Regex regex, uint versionOutputLine = 0, Log? log = null)
+ : base (programName, versionArguments, versionOutputLine, log)
+ {
+ if (regex == null)
+ throw new ArgumentNullException (nameof (regex));
+ rx = regex;
+ }
- public RegexProgramVersionParser (string programName, string versionArguments, string regex, uint versionOutputLine = 0, Log? log = null)
- : base (programName, versionArguments, versionOutputLine, log)
- {
- if (String.IsNullOrEmpty (regex))
- throw new ArgumentException ("must not be null or empty", nameof (regex));
+ public RegexProgramVersionParser (string programName, string versionArguments, string regex, uint versionOutputLine = 0, Log? log = null)
+ : base (programName, versionArguments, versionOutputLine, log)
+ {
+ if (String.IsNullOrEmpty (regex))
+ throw new ArgumentException ("must not be null or empty", nameof (regex));
- rx = new Regex (regex, RegexOptions.Compiled);
- }
+ rx = new Regex (regex, RegexOptions.Compiled);
+ }
- protected override string ParseVersion (string programOutput)
- {
- string output = programOutput.Trim ();
- if (String.IsNullOrEmpty (output)) {
- Log.WarningLine ($"Unable to parse version of {ProgramName} because version output was empty");
- return DefaultVersionString;
- }
+ protected override string ParseVersion (string programOutput)
+ {
+ string output = programOutput.Trim ();
+ if (String.IsNullOrEmpty (output)) {
+ Log.WarningLine ($"Unable to parse version of {ProgramName} because version output was empty");
+ return DefaultVersionString;
+ }
- string ret = String.Empty;
- string[] lines = programOutput.Split (LineSeparator);
- if (VersionOutputLine > 0) {
- if (lines.Length < VersionOutputLine) {
- Log.WarningLine ($"Not enough lines in version output of {ProgramName}: version number was supposed to be found on line {VersionOutputLine} but there are only {lines.Length} lines");
- return DefaultVersionString;
- }
+ string ret = String.Empty;
+ string[] lines = programOutput.Split (LineSeparator);
+ if (VersionOutputLine > 0) {
+ if (lines.Length < VersionOutputLine) {
+ Log.WarningLine ($"Not enough lines in version output of {ProgramName}: version number was supposed to be found on line {VersionOutputLine} but there are only {lines.Length} lines");
+ return DefaultVersionString;
+ }
- if (TryMatch (lines [VersionOutputLine - 1], out ret) && !String.IsNullOrEmpty (ret)) {
- return ret;
- }
+ if (TryMatch (rx, lines [VersionOutputLine - 1], out ret) && !String.IsNullOrEmpty (ret)) {
+ return ret;
+ }
- return DefaultVersionString;
- }
+ return DefaultVersionString;
+ }
- foreach (string line in lines) {
- if (TryMatch (line, out ret))
- break;
- }
+ foreach (string line in lines) {
+ if (TryMatch (rx, line, out ret))
+ break;
+ }
- return ret ?? DefaultVersionString;
- }
+ return ret ?? DefaultVersionString;
+ }
- bool TryMatch (string line, out string version)
- {
- version = String.Empty;
+ public static bool TryMatch (Regex regex, string line, out string version)
+ {
+ version = String.Empty;
- Match match = rx.Match (line);
- if (!match.Success || match.Groups.Count <= 0) {
- return false;
- }
+ Match match = regex.Match (line);
+ if (!match.Success || match.Groups.Count <= 0) {
+ return false;
+ }
- foreach (Group group in match.Groups) {
- if (String.Compare (group.Name, VersionGroupName, StringComparison.OrdinalIgnoreCase) == 0) {
- version = group.Value;
- return true;
- }
- }
+ foreach (Group group in match.Groups) {
+ if (String.Compare (group.Name, VersionGroupName, StringComparison.OrdinalIgnoreCase) == 0) {
+ version = group.Value;
+ return true;
+ }
+ }
- return false;
- }
- }
+ return false;
+ }
+ }
}
diff --git a/build-tools/xaprepare/xaprepare/Application/SevenZipVersionParser.cs b/build-tools/xaprepare/xaprepare/Application/SevenZipVersionParser.cs
new file mode 100644
index 00000000000..86bc518071a
--- /dev/null
+++ b/build-tools/xaprepare/xaprepare/Application/SevenZipVersionParser.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Text.RegularExpressions;
+
+namespace Xamarin.Android.Prepare;
+
+class SevenZipVersionParser : ProgramVersionParser
+{
+ const string VersionArgument = "--help";
+ readonly Regex fallbackRegex;
+ readonly Regex modernRegex;
+
+ public SevenZipVersionParser (string programName, Regex fallbackRegex, Log? log = null)
+ : base (programName, VersionArgument, 0, log)
+ {
+ this.fallbackRegex = fallbackRegex;
+ modernRegex = VersionFetchers.MakeRegex (@"^7-Zip (\(a\) ){0,1}(?[\d]+\.[\d]+)");
+ }
+
+ protected override string ParseVersion (string programOutput)
+ {
+ string output = programOutput.Trim ();
+ if (String.IsNullOrEmpty (output)) {
+ Log.WarningLine ($"Unable to parse version of {ProgramName} because version output was empty");
+ return DefaultVersionString;
+ }
+
+ string ret = String.Empty;
+ string[] lines = programOutput.Split (RegexProgramVersionParser.LineSeparator);
+
+ // First try to find the official 7zip release version
+ foreach (string l in lines) {
+ string line = l.Trim ();
+
+ if (line.Length == 0) {
+ continue;
+ }
+
+ if (line.StartsWith ("7-Zip", StringComparison.OrdinalIgnoreCase)) {
+ // Strings of the form:
+ // 7-Zip 23.01 (x64) : Copyright (c) 1999-2023 Igor Pavlov : 2023-06-20
+ // 7-Zip (a) 23.01 (x64) : Copyright (c) 1999-2023 Igor Pavlov : 2023-06-20
+ // 7-Zip (a) 18.01 (x64) : Copyright (c) 1999-2018 Igor Pavlov : 2018-01-28
+ // 7-Zip (a) 18.01 (x86) : Copyright (c) 1999-2018 Igor Pavlov : 2018-01-28
+ if (RegexProgramVersionParser.TryMatch (modernRegex, line, out ret)) {
+ return ret;
+ }
+ }
+
+ // Since we know we're dealing with `--help` option output, we can short-circuit things
+ if (line.StartsWith ("Usage:", StringComparison.OrdinalIgnoreCase)) {
+ break;
+ }
+ }
+
+ // Modern version wasn't found, try again with the fallback one
+ foreach (string l in lines) {
+ string line = l.Trim ();
+
+ if (line.Length == 0) {
+ continue;
+ }
+
+ if (RegexProgramVersionParser.TryMatch (fallbackRegex, line, out ret)) {
+ return ret;
+ }
+ }
+
+ return DefaultVersionString;
+ }
+}
diff --git a/build-tools/xaprepare/xaprepare/Application/VersionFetchers.cs b/build-tools/xaprepare/xaprepare/Application/VersionFetchers.cs
index 0125ff33b06..7b0a95b639e 100644
--- a/build-tools/xaprepare/xaprepare/Application/VersionFetchers.cs
+++ b/build-tools/xaprepare/xaprepare/Application/VersionFetchers.cs
@@ -18,7 +18,7 @@ class VersionFetchers
public Dictionary Fetchers => GetFetchers ();
- static Regex MakeRegex (string regex)
+ internal static Regex MakeRegex (string regex)
{
return new Regex (regex, RegexOptions.Compiled | RegexOptions.Singleline);
}
@@ -30,8 +30,11 @@ static Regex MakeRegex (string regex)
return fetchers;
fetchers = new Dictionary (Context.Instance.OS.DefaultStringComparer) {
- {"7z", "--help", MakeRegex ($"Version {StandardVersionRegex}"), 3},
- {"7za", "--help", MakeRegex ($"Version {StandardVersionRegex}"), 3},
+ // Program-specific parsers
+ {"7z", new SevenZipVersionParser ("7z", MakeRegex ($"Version {StandardVersionRegex}"))},
+ {"7za", new SevenZipVersionParser ("7za", MakeRegex ($"Version {StandardVersionRegex}"))},
+
+ // Regex parsers
{"autoconf", "--version", StandardVersionAtEOL, 1},
{"automake", "--version", StandardVersionAtEOL, 1},
{"brew", "--version", MakeRegex ($"^Homebrew {StandardVersionRegex}"), 1},
diff --git a/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs b/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs
index 4e025f17239..e27b85d5b26 100644
--- a/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs
+++ b/build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs
@@ -10,6 +10,10 @@ partial class SevenZipRunner : ToolRunner
{
const double DefaultTimeout = 30; // minutes
static readonly Version bsoepMinVersion = new Version (15, 5);
+
+ // Just an educated guess. The official download page had versions 19 and then 23+ available
+ // and the 19 one didn't support the `-snld` switch
+ static readonly Version snldMinVersion = new Version (20, 0);
Version version;
protected override string DefaultToolExecutableName => "7za";
@@ -36,6 +40,7 @@ public async Task Extract (string archivePath, string outputDirectory, Lis
throw new ArgumentException ("must not be null or empty", nameof (outputDirectory));
ProcessRunner runner = CreateProcessRunner ("x");
+
AddStandardArguments (runner);
AddArguments (runner, extraArguments);
runner.AddQuotedArgument ($"-o{outputDirectory}");
@@ -114,6 +119,18 @@ protected override TextWriter CreateLogSink (string? logFilePath)
void AddStandardArguments (ProcessRunner runner)
{
+ Log.DebugLine ($"7-zip standard arguments, for 7z version {version}");
+
+ // Ignore some "dangerous" symbolic symlinks in the ZIP archives. This allows 7zip to unpack Android NDK archives
+ // without error. The option appears to be undocumented, but was mentioned by the 7zip author here:
+ //
+ // https://sourceforge.net/p/sevenzip/discussion/45798/thread/187ce54fb0/
+ //
+ if (version >= snldMinVersion) {
+ Log.DebugLine ("Adding option to ignore dangerous symlinks");
+ runner.AddArgument ("-snld");
+ }
+
// Disable progress indicator (doesn't appear to have any effect with some versions of 7z)
runner.AddArgument ("-bd");
diff --git a/build-tools/xaprepare/xaprepare/xaprepare.csproj b/build-tools/xaprepare/xaprepare/xaprepare.csproj
index 41766677fdd..ffa96b5c34c 100644
--- a/build-tools/xaprepare/xaprepare/xaprepare.csproj
+++ b/build-tools/xaprepare/xaprepare/xaprepare.csproj
@@ -1,17 +1,16 @@
+
$(DotNetStableTargetFramework)
Exe
- 8.0
+ $(LangVersion)
Xamarin.Android.Prepare
xaprepare
true
enable
-
-