Linking static library based on architecture (arm-7 and arm64)

Hey everyone,

Hopefully, I can get some help on this as I really can’t seem to find a way to do this. So right now I have a plugin I published on the marketplace which uses a static library which I link inside the plugin’s build.cs file. I have the static library compiled for arm-7 and arm-64, however, I can’t seem to find a way to detect the two architectures inside the build.cs file. So if I link the two at the same time when I try packaging for one the other one will complain that it isn’t supported

Things I tried
Target.Platform string returns simply Android whereas on Windows returns Win64 and Win32 making it easier to link the right file.

Target.Architecture returns an empty string on all platforms so it’s literally useless.

I can see that on the forum “**Chris Babcock” **suggested to someone to use APL (this is back in 2014), however, in that instance, the person asking for help wanted to copy dynamic libraries that get linked at runtime.

I have done literally as much reading on this as I could and went through possibly all the plugins that make any use of libraries in the engine source code and didn’t find a single instance in which APL is used to specify the right architecture and then correctly link the right static library, it seems to be only used to copy dynamic libraries in the right folder so that they be linked at runtime.

So the questions are
Is there any way to detect whether we are building for arm7 or arm64 inside the build.cs so that I can link the correct library?
Am I possibly doing this completely wrong and there is another way of doing it?

I’ve run into the same issue. How can we build Android apps targeting multiple architectures, that statically link in things like libz.a, for example? I’m thinking that Build.cs is not the place to handle this, even though it does handle shared architecture-specific libs with some APL trickery. I’d appreciate any pointers on where to go from here for static libs.

Libz is just an example … I have custom .a files that need to be linked in :slight_smile:

Thanks!

Figured it out. This is handled in AndroidToolChain.cs. There’s a line in there for ignoring shared libraries of the wrong architecture, and it just needs to be expanded to also ignore static libs. Change:


            // deal with .so files with wrong architecture
            if (Path.GetExtension(Lib) == ".so")

to


            // deal with .so and .a files with wrong architecture
            if (Path.GetExtension(Lib) == ".so" || Path.GetExtension(Lib) == ".a")

That worked for me.

Hey, nice one figuring this out and thanks for taking the time to write the solution down. The only problem I have with that is that the solution is based on changing the engine source code and since I’m making a plugin it doesn’t solve the issue for me.
That should be a pull request on Github so that future engine versions don’t have the same issues.

In my case, I ended up writing some code checks for changes on the arm-7 arm-64 tick boxes in the project settings. When either of them is clicked I inject the right line of code inside the build.cs file of the plugin. It’s not elegant and it’s brute force but at least it solves the issue.

My solution was to create a c# class to read ini files, get the project dir + “/config/defaultengine.ini” file and grab the values:



/// <summary>
/// A class for reading values by section and key from a standard ".ini" initialization file.
/// </summary>
/// <remarks>
/// Section and key names are not case-sensitive. Values are loaded into a hash table for fast access.
/// Use <see cref="GetAllValues"/> to read multiple values that share the same section and key.
/// Sections in the initialization file must have the following form:
/// <code>
///     ; comment line
///     [section]
///     key=value
/// </code>
/// </remarks>
public class IniFile
{
    /// <summary>
    /// Initializes a new instance of the <see cref="IniFile"/> class.
    /// </summary>
    /// <param name="file">The initialization file path.</param>
    /// <param name="commentDelimiter">The comment delimiter string (default value is ";").
    /// </param>
    public IniFile(string file, string commentDelimiter = ";")
    {
        CommentDelimiter = commentDelimiter;
        TheFile = file;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="IniFile"/> class.
    /// </summary>
    public IniFile()
    {
        CommentDelimiter = ";";
    }

    /// <summary>
    /// The comment delimiter string (default value is ";").
    /// </summary>
    public string CommentDelimiter { get; set; }

    private string theFile;

    /// <summary>
    /// The initialization file path.
    /// </summary>
    public string TheFile
    {
        get
        {
            return theFile;
        }
        set
        {
            theFile = null;
            dictionary.Clear();
            if (File.Exists(value))
            {
                theFile = value;
                using (StreamReader sr = new StreamReader(theFile))
                {
                    string line, section = "";
                    while ((line = sr.ReadLine()) != null)
                    {
                        line = line.Trim();
                        if (line.Length == 0) continue;  // empty line
                        if (!String.IsNullOrEmpty(CommentDelimiter) && line.StartsWith(CommentDelimiter))
                            continue;  // comment

                        if (line.StartsWith("") && line.Contains("]"))  // [section]
                        {
                            int index = line.IndexOf(']');
                            section = line.Substring(1, index - 1).Trim();
                            continue;
                        }

                        if (line.Contains("="))  // key=value
                        {
                            int index = line.IndexOf('=');
                            string key = line.Substring(0, index).Trim();
                            string val = line.Substring(index + 1).Trim();
                            string key2 = String.Format("{0}]{1}", section, key).ToLower();

                            if (val.StartsWith("\"") && val.EndsWith("\""))  // strip quotes
                                val = val.Substring(1, val.Length - 2);

                            if (dictionary.ContainsKey(key2))  // multiple values can share the same key
                            {
                                index = 1;
                                string key3;
                                while (true)
                                {
                                    key3 = String.Format("{0}~{1}", key2, ++index);
                                    if (!dictionary.ContainsKey(key3))
                                    {
                                        dictionary.Add(key3, val);
                                        break;
                                    }
                                }
                            }
                            else
                            {
                                dictionary.Add(key2, val);
                            }
                        }
                    }
                }
            }
        }
    }

    // "[section]key"   -> "value1"
    // "[section]key~2" -> "value2"
    // "[section]key~3" -> "value3"
    private Dictionary<string, string> dictionary = new Dictionary<string, string>();

    private bool TryGetValue(string section, string key, out string value)
    {
        string key2;
        if (section.StartsWith(""))
            key2 = String.Format("{0}{1}", section, key);
        else
            key2 = String.Format("{0}]{1}", section, key);

        return dictionary.TryGetValue(key2.ToLower(), out value);
    }

    /// <summary>
    /// Gets a string value by section and key.
    /// </summary>
    /// <param name="section">The section.</param>
    /// <param name="key">The key.</param>
    /// <param name="defaultValue">The default value.</param>
    /// <returns>The value.</returns>
    /// <seealso cref="GetAllValues"/>
    public string GetValue(string section, string key, string defaultValue = "")
    {
        string value;
        if (!TryGetValue(section, key, out value))
            return defaultValue;

        return value;
    }

    /// <summary>
    /// Gets a string value by section and key.
    /// </summary>
    /// <param name="section">The section.</param>
    /// <param name="key">The key.</param>
    /// <returns>The value.</returns>
    /// <seealso cref="GetValue"/>
    public string this[string section, string key]
    {
        get
        {
            return GetValue(section, key);
        }
    }

    /// <summary>
    /// Gets an integer value by section and key.
    /// </summary>
    /// <param name="section">The section.</param>
    /// <param name="key">The key.</param>
    /// <param name="defaultValue">The default value.</param>
    /// <param name="minValue">Optional minimum value to be enforced.</param>
    /// <param name="maxValue">Optional maximum value to be enforced.</param>
    /// <returns>The value.</returns>
    public int GetInteger(string section, string key, int defaultValue = 0,
        int minValue = int.MinValue, int maxValue = int.MaxValue)
    {
        string stringValue;
        if (!TryGetValue(section, key, out stringValue))
            return defaultValue;

        int value;
        if (!int.TryParse(stringValue, out value))
        {
            double dvalue;
            if (!double.TryParse(stringValue, out dvalue))
                return defaultValue;
            value = (int)dvalue;
        }

        if (value < minValue)
            value = minValue;
        if (value > maxValue)
            value = maxValue;
        return value;
    }

    /// <summary>
    /// Gets a double floating-point value by section and key.
    /// </summary>
    /// <param name="section">The section.</param>
    /// <param name="key">The key.</param>
    /// <param name="defaultValue">The default value.</param>
    /// <param name="minValue">Optional minimum value to be enforced.</param>
    /// <param name="maxValue">Optional maximum value to be enforced.</param>
    /// <returns>The value.</returns>
    public double GetDouble(string section, string key, double defaultValue = 0,
        double minValue = double.MinValue, double maxValue = double.MaxValue)
    {
        string stringValue;
        if (!TryGetValue(section, key, out stringValue))
            return defaultValue;

        double value;
        if (!double.TryParse(stringValue, out value))
            return defaultValue;

        if (value < minValue)
            value = minValue;
        if (value > maxValue)
            value = maxValue;
        return value;
    }

    /// <summary>
    /// Gets a boolean value by section and key.
    /// </summary>
    /// <param name="section">The section.</param>
    /// <param name="key">The key.</param>
    /// <param name="defaultValue">The default value.</param>
    /// <returns>The value.</returns>
    public bool GetBoolean(string section, string key, bool defaultValue = false)
    {
        string stringValue;
        if (!TryGetValue(section, key, out stringValue))
            return defaultValue;

        return (stringValue != "0" && !stringValue.StartsWith("f", true, null));
    }

    /// <summary>
    /// Gets an array of string values by section and key.
    /// </summary>
    /// <param name="section">The section.</param>
    /// <param name="key">The key.</param>
    /// <returns>The array of values, or null if none found.</returns>
    /// <seealso cref="GetValue"/>
    public string] GetAllValues(string section, string key)
    {
        string key2, key3, value;
        if (section.StartsWith(""))
            key2 = String.Format("{0}{1}", section, key).ToLower();
        else
            key2 = String.Format("{0}]{1}", section, key).ToLower();

        if (!dictionary.TryGetValue(key2, out value))
            return null;

        List<string> values = new List<string>();
        values.Add(value);
        int index = 1;
        while (true)
        {
            key3 = String.Format("{0}~{1}", key2, ++index);
            if (!dictionary.TryGetValue(key3, out value))
                break;
            values.Add(value);
        }

        return values.ToArray();
    }
}



example of use:


 
var defaultEngineIniFile = new IniFile(target.ProjectFile.Directory.FullName + "/Config/DefaultEngine.ini");
bool bBuildForArmV7 = defaultEngineIniFile.GetBoolean("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings",
            "bBuildForArmV7", true);
        bool bBuildForArm64 =
            defaultEngineIniFile.GetBoolean("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bBuildForArm64", false);