premake-core icon indicating copy to clipboard operation
premake-core copied to clipboard

[docs] Add warning for `os.hostarch`

Open mercury233 opened this issue 7 months ago • 11 comments

What does this PR do?

Currently, there are issues with both the os.hostarch function and its documentation. The problem with the function is that it doesn't actually retrieve the runtime architecture, and the issue with the documentation is that it fails to point this out.

How does this PR change Premake's behavior?

Nothing.

Anything else we should know?

I think it is better to fix os.hostarch to reflect the actual arch.

Did you check all the boxes?

  • [x] Focus on a single fix or feature; remove any unrelated formatting or code changes
  • [x] Add unit tests showing fix or feature works; all tests pass
  • [x] Mention any related issues (put closes #XXXX in comment to auto-close issue when PR is merged)
  • [x] Follow our coding conventions
  • [x] Minimize the number of commits
  • [x] Align documentation to your changes

You can now support Premake on our OpenCollective. Your contributions help us spend more time responding to requests like these!

mercury233 avatar May 17 '25 08:05 mercury233

Possible fix, untested:

/**
 * \file   os_hostarch.c
 * \brief  Get the architecture for the current host OS we're executing on.
 * \author Copyright (c) 2014-2024 Jess Perkins and the Premake project
 */

#include "premake.h"

#if defined(_WIN32)
  #include <windows.h>
#elif defined(__unix__) || defined(__APPLE__)
  #include <sys/utsname.h>
  #include <string.h>
#endif

/**
 * Detect the actual CPU architecture at runtime.
 * Falls back to compile-time PLATFORM_ARCHITECTURE on failure.
 */
static const char *detect_runtime_arch(void)
{
#if defined(_WIN32)
    // Try the newer IsWow64Process2 (Windows 10+)
    HMODULE hKernel = GetModuleHandleA("kernel32");
    if (hKernel) {
        typedef BOOL (WINAPI *LPFN_IsWow64Process2)(HANDLE, USHORT*, USHORT*);
        LPFN_IsWow64Process2 fnWow2 =
            (LPFN_IsWow64Process2)GetProcAddress(hKernel, "IsWow64Process2");
        if (fnWow2) {
            USHORT procMachine = 0, nativeMachine = 0;
            if (fnWow2(GetCurrentProcess(), &procMachine, &nativeMachine)) {
                switch (nativeMachine) {
                    case IMAGE_FILE_MACHINE_AMD64:  return "x86_64";
                    case IMAGE_FILE_MACHINE_I386:   return "x86";
                    case IMAGE_FILE_MACHINE_ARM64:  return "ARM64";
                    case IMAGE_FILE_MACHINE_ARM:    return "ARM";
                    default:                        return "unknown";
                }
            }
        }
    }
    // Fallback to GetNativeSystemInfo
    {
        SYSTEM_INFO si;
        typedef void (WINAPI *LPFN_GetNativeSystemInfo)(LPSYSTEM_INFO);
        LPFN_GetNativeSystemInfo fnNSI =
            (LPFN_GetNativeSystemInfo)GetProcAddress(hKernel, "GetNativeSystemInfo");
        if (fnNSI)
            fnNSI(&si);
        else
            GetSystemInfo(&si);

        switch (si.wProcessorArchitecture) {
            case PROCESSOR_ARCHITECTURE_AMD64:  return "x86_64";
            case PROCESSOR_ARCHITECTURE_INTEL:  return "x86";
            case PROCESSOR_ARCHITECTURE_ARM64:  return "ARM64";
            case PROCESSOR_ARCHITECTURE_ARM:    return "ARM";
            default:                            return "unknown";
        }
    }

#elif defined(__unix__) || defined(__APPLE__)
    struct utsname u;
    if (uname(&u) == 0 && u.machine[0] != '\0') {
        const char *m = u.machine;
        // Normalize uname values to our PLATFORM_ARCHITECTURE strings
        if (strcmp(m, "x86_64") == 0 || strcmp(m, "amd64") == 0)
            return "x86_64";
        else if (strcmp(m, "i386") == 0 || strcmp(m, "i486") == 0 || strcmp(m, "i586") == 0 || strcmp(m, "i686") == 0)
            return "x86";
        else if (strcmp(m, "aarch64") == 0 || strcmp(m, "arm64") == 0)
            return "ARM64";
        else if (strncmp(m, "arm", 3) == 0)
            return "ARM";
        else if (strcmp(m, "riscv64") == 0)
            return "RISCV64";
        else if (strcmp(m, "loongarch64") == 0)
            return "loongarch64";
        else if (strcmp(m, "e2k") == 0)
            return "e2k";
        // Unknown or less common: return raw uname
        return m;
    } else {
        /* Fallback to compile-time macro */
        return PLATFORM_ARCHITECTURE;
    }

#else
    /* Other platforms: fallback */
    return PLATFORM_ARCHITECTURE;
#endif
}

int os_hostarch(lua_State* L)
{
    const char *arch = detect_runtime_arch();
    lua_pushstring(L, arch);
    return 1;
}

tritao avatar May 17 '25 19:05 tritao

Possible fix, untested:

/**
 * \file   os_hostarch.c
 * \brief  Get the architecture for the current host OS we're executing on.
 * \author Copyright (c) 2014-2024 Jess Perkins and the Premake project
 */

#include "premake.h"

#if defined(_WIN32)
  #include <windows.h>
#elif defined(__unix__) || defined(__APPLE__)
  #include <sys/utsname.h>
  #include <string.h>
#endif

/**
 * Detect the actual CPU architecture at runtime.
 * Falls back to compile-time PLATFORM_ARCHITECTURE on failure.
 */
static const char *detect_runtime_arch(void)
{
#if defined(_WIN32)
    // Try the newer IsWow64Process2 (Windows 10+)
    HMODULE hKernel = GetModuleHandleA("kernel32");
    if (hKernel) {
        typedef BOOL (WINAPI *LPFN_IsWow64Process2)(HANDLE, USHORT*, USHORT*);
        LPFN_IsWow64Process2 fnWow2 =
            (LPFN_IsWow64Process2)GetProcAddress(hKernel, "IsWow64Process2");
        if (fnWow2) {
            USHORT procMachine = 0, nativeMachine = 0;
            if (fnWow2(GetCurrentProcess(), &procMachine, &nativeMachine)) {
                switch (nativeMachine) {
                    case IMAGE_FILE_MACHINE_AMD64:  return "x86_64";
                    case IMAGE_FILE_MACHINE_I386:   return "x86";
                    case IMAGE_FILE_MACHINE_ARM64:  return "ARM64";
                    case IMAGE_FILE_MACHINE_ARM:    return "ARM";
                    default:                        return "unknown";
                }
            }
        }
    }
    // Fallback to GetNativeSystemInfo
    {
        SYSTEM_INFO si;
        typedef void (WINAPI *LPFN_GetNativeSystemInfo)(LPSYSTEM_INFO);
        LPFN_GetNativeSystemInfo fnNSI =
            (LPFN_GetNativeSystemInfo)GetProcAddress(hKernel, "GetNativeSystemInfo");
        if (fnNSI)
            fnNSI(&si);
        else
            GetSystemInfo(&si);

        switch (si.wProcessorArchitecture) {
            case PROCESSOR_ARCHITECTURE_AMD64:  return "x86_64";
            case PROCESSOR_ARCHITECTURE_INTEL:  return "x86";
            case PROCESSOR_ARCHITECTURE_ARM64:  return "ARM64";
            case PROCESSOR_ARCHITECTURE_ARM:    return "ARM";
            default:                            return "unknown";
        }
    }

#elif defined(__unix__) || defined(__APPLE__)
    struct utsname u;
    if (uname(&u) == 0 && u.machine[0] != '\0') {
        const char *m = u.machine;
        // Normalize uname values to our PLATFORM_ARCHITECTURE strings
        if (strcmp(m, "x86_64") == 0 || strcmp(m, "amd64") == 0)
            return "x86_64";
        else if (strcmp(m, "i386") == 0 || strcmp(m, "i486") == 0 || strcmp(m, "i586") == 0 || strcmp(m, "i686") == 0)
            return "x86";
        else if (strcmp(m, "aarch64") == 0 || strcmp(m, "arm64") == 0)
            return "ARM64";
        else if (strncmp(m, "arm", 3) == 0)
            return "ARM";
        else if (strcmp(m, "riscv64") == 0)
            return "RISCV64";
        else if (strcmp(m, "loongarch64") == 0)
            return "loongarch64";
        else if (strcmp(m, "e2k") == 0)
            return "e2k";
        // Unknown or less common: return raw uname
        return m;
    } else {
        /* Fallback to compile-time macro */
        return PLATFORM_ARCHITECTURE;
    }

#else
    /* Other platforms: fallback */
    return PLATFORM_ARCHITECTURE;
#endif
}

int os_hostarch(lua_State* L)
{
    const char *arch = detect_runtime_arch();
    lua_pushstring(L, arch);
    return 1;
}

Unfortunately, this would break any existing code relying on the existing behavior (for some reason). We need to first deprecate the API before changing its functionality entirely.

nickclark2016 avatar May 17 '25 19:05 nickclark2016

Unfortunately, this would break any existing code relying on the existing behavior (for some reason). We need to first deprecate the API before changing its functionality entirely.

This seems such a niche edge-case that I think it's reasonable to just fix it. The fact no one else has complained about it in years suggests maybe no one else has found this before?

And even then, it's not that common to filter builds by architecture, and if one did, such filtering with the current behaviour would be wrong anyway, with no alternative to properly filter it as you could not differentiate between the right architecture.

Going through a deprecating cycle for this seems overkill. What would you replace it with anyway? Just add an os.hostarch2 and break everything else? Seems more reasonable to just fix it and document the behaviour in the release notes, but just my 2 cents.

tritao avatar May 17 '25 20:05 tritao

Doing things based on premake host architecture also feels like an anti-pattern. I agree, this is definitely an edge case. I don't know what we would name and deprecate with. The correct solution may be to just fix it directly, since we are still just in beta and can have the breaking change.

nickclark2016 avatar May 17 '25 20:05 nickclark2016

I'm afraid filtering by architecture isn't particularly uncommon. Macs, for example, come with either Apple Silicon or Intel CPUs, corresponding to ARM64 and x86_64 architectures respectively. Certain x86-specific optimizations, such as SSE, may not be includable in builds targeting the ARM64 architecture, and vice versa.

I understand that in such cases it might be more appropriate to use os.targetarch, but that function doesn't work either—though the documentation clearly states this. https://github.com/mercury233/premake-macos-arch-test/actions/runs/15089710004/job/42416629418#step:5:1

Moreover, the architecture directive itself doesn't function fully as expected; see issue #2136.

mercury233 avatar May 17 '25 22:05 mercury233

You should likely be filtering using filter "architecture:arm", for example. I'm saying it's probably an antipattern to filter based on the architecture of the computer running premake. It's perfectly reasonable (and expected) to filter based on the target system's architecture (via the filter tag)

nickclark2016 avatar May 17 '25 22:05 nickclark2016

You should likely be filtering using filter "architecture:arm", for example. I'm saying it's probably an antipattern to filter based on the architecture of the computer running premake. It's perfectly reasonable (and expected) to filter based on the target system's architecture (via the filter tag)

https://github.com/mercury233/premake-macos-arch-test/actions/runs/15089901144/job/42417030453#step:7:8

The filter only takes effect when architecture has been set. As for how to determine the architecture, if we want the compiled program to match the architecture of the machine doing the compiling (This is the current default behavior of Premake on macOS.), we need to use os.hostarch.

mercury233 avatar May 17 '25 23:05 mercury233

Okay, yeah, let's fix the actual issue then rather than updating docs (as @tritao suggested)

nickclark2016 avatar May 18 '25 18:05 nickclark2016

Can you try the suggested code @mercury233?

tritao avatar May 18 '25 22:05 tritao

Just wanted to poke on this. An update to the docs is better than nothing, but if we can fix the root of the problem, that would be best.

nickclark2016 avatar May 23 '25 15:05 nickclark2016

Can you try the suggested code @mercury233?

I finally had the chance to test your patch. I set up an ARM-based TV box and installed the Armbian Linux system on it.

https://github.com/mercury233/premake-arch-test

This repository contains the program I compiled using your patch. Here are the test results:

https://github.com/mercury233/premake-arch-test/actions/runs/15649386571/job/44091879546

It successfully detected the current architecture on both Linux and Windows, but not on macOS. The original beta7 version, however, is a universal build, so it can correctly detect the architecture on macOS.

mercury233 avatar Jun 14 '25 06:06 mercury233