Repository: conan-io/conan
Pull request #17898 (2025-03-05 17:46:04)

Premake generator: better integration
Changelog: Feature: Add premake toolchain and improved premake integration in conan with new premake5
Docs: https://github.com/conan-io/docs/pull/4090

Follow https://github.com/premake/premake-core/discussions/2441
And test project: https://github.com/perseoGI/conan-premake-example



Patch:
diff --git a/conan/api/subapi/new.py b/conan/api/subapi/new.py
index eb961a836..f3fc5978d 100644
--- a/conan/api/subapi/new.py
+++ b/conan/api/subapi/new.py
@@ -96,6 +96,8 @@ class NewAPI:
         from conan.internal.api.new.bazel_7_exe import bazel_exe_files_7
         from conan.internal.api.new.autotools_lib import autotools_lib_files
         from conan.internal.api.new.autoools_exe import autotools_exe_files
+        from conan.internal.api.new.premake_lib import premake_lib_files
+        from conan.internal.api.new.premake_exe import premake_exe_files
         from conan.internal.api.new.local_recipes_index import local_recipes_index_files
         from conan.internal.api.new.qbs_lib import qbs_lib_files
         from conan.internal.api.new.workspace import workspace_files
@@ -114,6 +116,8 @@ class NewAPI:
                          "bazel_7_exe": bazel_exe_files_7,
                          "autotools_lib": autotools_lib_files,
                          "autotools_exe": autotools_exe_files,
+                         "premake_lib": premake_lib_files,
+                         "premake_exe": premake_exe_files,
                          "alias": alias_file,
                          "local_recipes_index": local_recipes_index_files,
                          "qbs_lib": qbs_lib_files,
diff --git a/conan/internal/api/install/generators.py b/conan/internal/api/install/generators.py
index e369c4449..4e7093875 100644
--- a/conan/internal/api/install/generators.py
+++ b/conan/internal/api/install/generators.py
@@ -30,6 +30,7 @@ _generators = {"CMakeToolchain": "conan.tools.cmake",
                "XcodeDeps": "conan.tools.apple",
                "XcodeToolchain": "conan.tools.apple",
                "PremakeDeps": "conan.tools.premake",
+               "PremakeToolchain": "conan.tools.premake",
                "MakeDeps": "conan.tools.gnu",
                "SConsDeps": "conan.tools.scons",
                "QbsDeps": "conan.tools.qbs",
diff --git a/conan/internal/api/new/premake_exe.py b/conan/internal/api/new/premake_exe.py
new file mode 100644
index 000000000..cbc58135c
--- /dev/null
+++ b/conan/internal/api/new/premake_exe.py
@@ -0,0 +1,104 @@
+from conan.internal.api.new.cmake_lib import source_cpp, source_h, test_main
+
+conanfile_exe = """import os
+from conan import ConanFile
+from conan.tools.files import copy
+from conan.tools.layout import basic_layout
+from conan.tools.premake import PremakeDeps, PremakeToolchain, Premake
+
+
+class {{package_name}}Recipe(ConanFile):
+    name = "{{name}}"
+    version = "{{version}}"
+    package_type = "application"
+
+    # Optional metadata
+    license = "<Put the package license here>"
+    author = "<Put your name here> <And your email here>"
+    url = "<Package recipe repository url here, for issues about the package>"
+    description = "<Description of {{ name }} package here>"
+    topics = ("<Put some tag here>", "<here>", "<and here>")
+
+    # Binary configuration
+    settings = "os", "compiler", "build_type", "arch"
+
+    # Sources are located in the same place as this recipe, copy them to the recipe
+    exports_sources = "premake5.lua", "src/*"
+
+    def layout(self):
+        basic_layout(self)
+
+    def generate(self):
+        deps = PremakeDeps(self)
+        deps.generate()
+        tc = PremakeToolchain(self)
+        tc.generate()
+
+    def build(self):
+        premake = Premake(self)
+        premake.configure()
+        premake.build(workspace="{{name.capitalize()}}")
+
+    def package(self):
+        dest_bin = os.path.join(self.package_folder, "bin")
+        build = os.path.join(self.build_folder, "bin")
+        copy(self, "{{name}}", build, dest_bin, keep_path=False)
+        copy(self, "{{name}}.exe", build, dest_bin, keep_path=False)
+
+
+    {% if requires is defined -%}
+    def requirements(self):
+        {% for require in requires -%}
+        self.requires("{{ require }}")
+        {% endfor %}
+    {%- endif %}
+
+    {% if tool_requires is defined -%}
+    def build_requirements(self):
+        {% for require in tool_requires -%}
+        self.tool_requires("{{ require }}")
+        {% endfor %}
+    {%- endif %}
+"""
+
+premake5 = """workspace "{{name.capitalize()}}"
+   configurations { "Debug", "Release" }
+
+project "{{name}}"
+    kind "ConsoleApp"
+    language "C++"
+    files { "src/main.cpp", "src/{{name}}.cpp", "src/{{name}}.h" }
+
+    filter "configurations:Debug"
+       defines { "DEBUG" }
+       symbols "On"
+
+    filter "configurations:Release"
+       defines { "NDEBUG" }
+       optimize "On"
+"""
+
+test_conanfile_exe_v2 = """
+from conan import ConanFile
+from conan.tools.build import can_run
+
+
+class {{package_name}}TestConan(ConanFile):
+    settings = "os", "compiler", "build_type", "arch"
+
+    def requirements(self):
+        self.requires(self.tested_reference_str)
+
+    def test(self):
+        if can_run(self):
+            self.run("{{name}}", env="conanrun")
+"""
+
+premake_exe_files = {
+    "conanfile.py": conanfile_exe,
+    "src/{{name}}.cpp": source_cpp,
+    "src/{{name}}.h": source_h,
+    "src/main.cpp": test_main,
+    "premake5.lua": premake5,
+    "test_package/conanfile.py": test_conanfile_exe_v2,
+}
diff --git a/conan/internal/api/new/premake_lib.py b/conan/internal/api/new/premake_lib.py
new file mode 100644
index 000000000..0ee88f3cc
--- /dev/null
+++ b/conan/internal/api/new/premake_lib.py
@@ -0,0 +1,148 @@
+from conan.internal.api.new.cmake_lib import source_cpp, source_h, test_main
+
+conanfile_sources = """import os
+from conan import ConanFile
+from conan.tools.files import copy
+from conan.tools.layout import basic_layout
+from conan.tools.premake import PremakeDeps, PremakeToolchain, Premake
+
+
+class {{package_name}}Recipe(ConanFile):
+    name = "{{name}}"
+    version = "{{version}}"
+    package_type = "library"
+
+    # Optional metadata
+    license = "<Put the package license here>"
+    author = "<Put your name here> <And your email here>"
+    url = "<Package recipe repository url here, for issues about the package>"
+    description = "<Description of {{ name }} package here>"
+    topics = ("<Put some tag here>", "<here>", "<and here>")
+
+    # Binary configuration
+    settings = "os", "compiler", "build_type", "arch"
+    options = {"shared": [True, False], "fPIC": [True, False]}
+    default_options = {"shared": False, "fPIC": True}
+    implements = ["auto_shared_fpic"]
+
+    # Sources are located in the same place as this recipe, copy them to the recipe
+    exports_sources = "premake5.lua", "src/*", "include/*"
+
+    def layout(self):
+        basic_layout(self)
+    {% if requires is defined %}
+    def requirements(self):
+        {% for require in requires -%}
+        self.requires("{{ require }}")
+        {% endfor %}
+    {%- endif %}
+    {%- if tool_requires is defined %}
+    def build_requirements(self):
+        {% for require in tool_requires -%}
+        self.tool_requires("{{ require }}")
+        {% endfor %}
+    {%- endif %}
+    def generate(self):
+        deps = PremakeDeps(self)
+        deps.generate()
+        tc = PremakeToolchain(self)
+        tc.generate()
+
+    def build(self):
+        premake = Premake(self)
+        premake.configure()
+        premake.build(workspace="{{name.capitalize()}}")
+
+    def package(self):
+        copy(self, "*.h", os.path.join(self.source_folder, "include"), os.path.join(self.package_folder, "include"))
+
+        for pattern in ("*.lib", "*.a", "*.so*", "*.dylib"):
+            copy(self, pattern, os.path.join(self.build_folder, "bin"), os.path.join(self.package_folder, "lib"))
+        copy(self, "*.dll", os.path.join(self.build_folder, "bin"), os.path.join(self.package_folder, "bin"))
+
+    def package_info(self):
+        self.cpp_info.libs = ["{{name}}"]
+"""
+
+premake5 = """workspace "{{name.capitalize()}}"
+    configurations { "Debug", "Release" }
+    fatalwarnings {"All"}
+    floatingpoint "Fast"
+    includedirs { ".", "src", "include" }
+
+project "{{name}}"
+    cppdialect "C++17"
+
+    -- To let conan take control over `kind` of the libraries, DO NOT SET `kind` (StaticLib or
+    -- SharedLib) in `project` block.
+    -- kind "<controlled by conan>"
+
+    language "C++"
+    files { "include/*.hpp", "include/*.h", "src/*.cpp" }
+
+    filter "configurations:Debug"
+       defines { "DEBUG" }
+       symbols "On"
+
+    filter "configurations:Release"
+       defines { "NDEBUG" }
+       optimize "On"
+
+"""
+
+test_conanfile = """import os
+
+from conan import ConanFile
+from conan.tools.layout import basic_layout
+from conan.tools.premake import Premake
+from conan.tools.build import can_run
+
+
+class {{package_name}}TestConan(ConanFile):
+    settings = "os", "compiler", "build_type", "arch"
+    generators = "PremakeDeps", "PremakeToolchain"
+
+    def layout(self):
+        basic_layout(self)
+
+    def requirements(self):
+        self.requires(self.tested_reference_str)
+
+    def build(self):
+        premake = Premake(self)
+        premake.configure()
+        premake.build(workspace="Example")
+
+    def test(self):
+        if can_run(self):
+            cmd = os.path.join(self.cpp.build.bindir, "bin", "example")
+            self.run(cmd, env="conanrun")
+"""
+
+test_premake5 = """workspace "Example"
+   configurations { "Debug", "Release" }
+
+project "example"
+   kind "ConsoleApp"
+   language "C++"
+   files { "src/example.cpp" }
+
+   filter "configurations:Debug"
+      defines { "DEBUG" }
+      symbols "On"
+
+   filter "configurations:Release"
+      defines { "NDEBUG" }
+      optimize "On"
+"""
+
+
+premake_lib_files = {
+    "conanfile.py": conanfile_sources,
+    "src/{{name}}.cpp": source_cpp,
+    "include/{{name}}.h": source_h,
+    "premake5.lua": premake5,
+    "test_package/conanfile.py": test_conanfile,
+    "test_package/src/example.cpp": test_main,
+    "test_package/premake5.lua": test_premake5,
+}
diff --git a/conan/tools/premake/__init__.py b/conan/tools/premake/__init__.py
index 339d38a6b..c51356c59 100644
--- a/conan/tools/premake/__init__.py
+++ b/conan/tools/premake/__init__.py
@@ -1,2 +1,3 @@
 from conan.tools.premake.premake import Premake
-from conan.tools.premake.premakedeps import PremakeDeps
\ No newline at end of file
+from conan.tools.premake.premakedeps import PremakeDeps
+from conan.tools.premake.toolchain import PremakeToolchain
diff --git a/conan/tools/premake/constants.py b/conan/tools/premake/constants.py
new file mode 100644
index 000000000..225a27773
--- /dev/null
+++ b/conan/tools/premake/constants.py
@@ -0,0 +1,33 @@
+# Source: https://premake.github.io/docs/architecture/
+CONAN_TO_PREMAKE_ARCH = {
+    "x86": "x86",
+    "x86_64": "x86_64",
+
+    "armv4": "arm",
+    "armv4i": "arm",
+    "armv5el": "arm",
+    "armv5hf": "arm",
+    "armv6": "arm",
+    "armv7": "arm",
+    "armv7hf": "arm",
+    "armv7s": "arm",
+    "armv7k": "arm",
+
+    "armv8": "arm64",
+    "armv8_32": "arm64",
+    "armv8.3": "arm64",
+    "arm64ec": "arm64",
+
+    "e2k-v2": "e2k",
+    "e2k-v3": "e2k",
+    "e2k-v4": "e2k",
+    "e2k-v5": "e2k",
+    "e2k-v6": "e2k",
+    "e2k-v7": "e2k",
+
+    "riscv64": "riscv64",
+
+    "wasm": "wasm32",
+    "wasm64": "wasm64",
+    "asm.js": "wasm32",
+}
diff --git a/conan/tools/premake/premake.py b/conan/tools/premake/premake.py
index 69765dd3c..606808063 100644
--- a/conan/tools/premake/premake.py
+++ b/conan/tools/premake/premake.py
@@ -1,3 +1,15 @@
+import textwrap
+from pathlib import Path
+
+from conan.tools.build.cpu import build_jobs
+from jinja2 import Template
+
+from conan.errors import ConanException
+from conan.tools.files import save
+from conan.tools.microsoft.msbuild import MSBuild
+from conan.tools.premake.toolchain import PremakeToolchain
+from conan.tools.premake.constants import CONAN_TO_PREMAKE_ARCH
+
 # Source: https://learn.microsoft.com/en-us/cpp/overview/compiler-versions?view=msvc-170
 PREMAKE_VS_VERSION = {
     '190': '2015',
@@ -7,34 +19,101 @@ PREMAKE_VS_VERSION = {
     '194': '2022',  # still 2022
 }
 
-
 class Premake:
     """
-    Premake cli wrapper
+    This class calls Premake commands when a package is being built. Notice that
+    this one should be used together with the ``PremakeToolchain`` generator.
+
+    This premake generator is only compatible with ``premake5``.
     """
 
+    filename = "conanfile.premake5.lua"
+
+    # Conan premake file which will preconfigure toolchain and then will call the user's premake file
+    _premake_file_template = textwrap.dedent(
+        """\
+    #!lua
+    include("{{luafile}}")
+    include("{{premake_conan_toolchain}}")
+    """
+    )
+
     def __init__(self, conanfile):
+        """
+        :param conanfile: ``< ConanFile object >`` The current recipe object. Always use ``self``.
+        """
         self._conanfile = conanfile
-
-        self.action = None              # premake5 action to use (Autogenerated)
-        self.luafile = 'premake5.lua'   # Path to the root (premake5) lua file
-        # (key value pairs. Will translate to "--{key}={value}")
-        self.arguments = {}             # https://premake.github.io/docs/Command-Line-Arguments/
+        #: Path to the root premake5 lua file (default is ``premake5.lua``)
+        self.luafile = (Path(self._conanfile.source_folder) / "premake5.lua").as_posix()
+        #: Key value pairs. Will translate to "--{key}={value}"
+        self.arguments = {}  # https://premake.github.io/docs/Command-Line-Arguments/
 
         if "msvc" in self._conanfile.settings.compiler:
             msvc_version = PREMAKE_VS_VERSION.get(str(self._conanfile.settings.compiler.version))
             self.action = f'vs{msvc_version}'
         else:
-            self.action = 'gmake2'
+            self.action = "gmake" # New generator (old gmakelegacy is deprecated)
+
+        self._premake_conan_toolchain = Path(self._conanfile.generators_folder) / PremakeToolchain.filename
 
     @staticmethod
     def _expand_args(args):
         return ' '.join([f'--{key}={value}' for key, value in args.items()])
 
     def configure(self):
+        """
+        Runs ``premake5 <action> [FILE]`` which will generate respective build scripts depending on the ``action``.
+        """
+        if self._premake_conan_toolchain.exists():
+            content = Template(self._premake_file_template).render(
+                premake_conan_toolchain=self._premake_conan_toolchain, luafile=self.luafile
+            )
+            conan_luafile = Path(self._conanfile.build_folder) / self.filename
+            save(self._conanfile, conan_luafile, content)
+            arch = str(self._conanfile.settings.arch)
+            if arch not in CONAN_TO_PREMAKE_ARCH:
+                raise ConanException(f"Premake does not support {arch} architecture.")
+            self.arguments["arch"] = CONAN_TO_PREMAKE_ARCH[arch]
+        else:
+            # Old behavior, for backward compatibility
+            conan_luafile = self.luafile
+
         premake_options = dict()
-        premake_options["file"] = self.luafile
+        premake_options["file"] = f'"{conan_luafile}"'
 
-        premake_command = f'premake5 {self._expand_args(premake_options)} {self.action} ' \
-                          f'{self._expand_args(self.arguments)}'
+        premake_command = (
+            f"premake5 {self._expand_args(premake_options)} {self.action} "
+            f"{self._expand_args(self.arguments)}{self._premake_verbosity}"
+        )
         self._conanfile.run(premake_command)
+
+    @property
+    def _premake_verbosity(self):
+        verbosity = self._conanfile.conf.get("tools.build:verbosity", choices=("quiet", "verbose"))
+        return " --verbose" if verbosity == "verbose" else ""
+
+    @property
+    def _compilation_verbosity(self):
+        verbosity = self._conanfile.conf.get("tools.compilation:verbosity", choices=("quiet", "verbose"))
+        # --verbose does not print compilation commands but internal Makefile progress logic
+        return " verbose=1" if verbosity == "verbose" else ""
+
+    def build(self, workspace, targets=None):
+        """
+        Depending on the action, this method will run either ``msbuild`` or ``make`` with ``N_JOBS``.
+        You can specify ``N_JOBS`` through the configuration line ``tools.build:jobs=N_JOBS``
+        in your profile ``[conf]`` section.
+
+        :param workspace: ``str`` Specifies the solution to be compiled (only used by ``MSBuild``).
+        :param targets: ``List[str]`` Declare the projects to be built (None to build all projects).
+        """
+        if not self._premake_conan_toolchain.exists():
+            raise ConanException(f"Premake.build() method requires PrmakeToolchain to work properly")
+        if self.action.startswith("vs"):
+            msbuild = MSBuild(self._conanfile)
+            msbuild.build(sln=f"{workspace}.sln", targets=targets)
+        else:
+            build_type = str(self._conanfile.settings.build_type)
+            targets = "all" if targets is None else " ".join(targets)
+            njobs = build_jobs(self._conanfile)
+            self._conanfile.run(f"make config={build_type.lower()} {targets} -j{njobs}{self._compilation_verbosity}")
diff --git a/conan/tools/premake/premakedeps.py b/conan/tools/premake/premakedeps.py
index dcd5cea83..80b291ac2 100644
--- a/conan/tools/premake/premakedeps.py
+++ b/conan/tools/premake/premakedeps.py
@@ -4,9 +4,10 @@ import re
 
 from conan.internal import check_duplicated_generator
 from conan.internal.util.files import save
+from conan.tools.premake.constants import CONAN_TO_PREMAKE_ARCH
 
 # Filename format strings
-PREMAKE_VAR_FILE = "conan_{pkgname}_vars{config}.premake5.lua"
+PREMAKE_VAR_FILE = "conan_{pkgname}_vars_{config}.premake5.lua"
 PREMAKE_CONF_FILE = "conan_{pkgname}{config}.premake5.lua"
 PREMAKE_PKG_FILE = "conan_{pkgname}.premake5.lua"
 PREMAKE_ROOT_FILE = "conandeps.premake5.lua"
@@ -145,10 +146,7 @@ class PremakeDeps:
             save(generator_file, content)
 
     def _config_suffix(self):
-        props = [("Configuration", self.configuration),
-                 ("Platform", self.architecture)]
-        name = "".join("_%s" % v for _, v in props)
-        return name.lower()
+        return f"{self.configuration}_{CONAN_TO_PREMAKE_ARCH[str(self.architecture)]}".lower()
 
     def _output_lua_file(self, filename, content):
         self.output_files[filename] = "\n".join(["#!lua", *content])
@@ -180,8 +178,7 @@ class PremakeDeps:
         check_duplicated_generator(self, self._conanfile)
 
         self.output_files = {}
-        conf_suffix = str(self._config_suffix())
-        conf_name = conf_suffix[1::]
+        conf_name = self._config_suffix()
 
         # Global utility file
         self._output_lua_file("conanutils.premake5.lua", [PREMAKE_TEMPLATE_UTILS])
@@ -206,15 +203,15 @@ class PremakeDeps:
             dep_aggregate = dep.cpp_info.aggregated_components()
 
             # Generate config dependent package variable and setup premake file
-            var_filename = PREMAKE_VAR_FILE.format(pkgname=dep_name, config=conf_suffix)
+            var_filename = PREMAKE_VAR_FILE.format(pkgname=dep_name, config=conf_name)
             self._output_lua_file(var_filename, [
                 PREMAKE_TEMPLATE_VAR.format(pkgname=dep_name,
                     config=conf_name, deps=_PremakeTemplate(dep_aggregate))
             ])
 
             # Create list of all available profiles by searching on disk
-            file_pattern = PREMAKE_VAR_FILE.format(pkgname=dep_name, config="_*")
-            file_regex = PREMAKE_VAR_FILE.format(pkgname=re.escape(dep_name), config="_(([^_]*)_(.*))")
+            file_pattern = PREMAKE_VAR_FILE.format(pkgname=dep_name, config="*")
+            file_regex = PREMAKE_VAR_FILE.format(pkgname=re.escape(dep_name), config="(([^_]*)_(.*))")
             available_files = glob.glob(file_pattern)
             # Add filename of current generations var file if not already present
             if var_filename not in available_files:
diff --git a/conan/tools/premake/toolchain.py b/conan/tools/premake/toolchain.py
new file mode 100644
index 000000000..346165240
--- /dev/null
+++ b/conan/tools/premake/toolchain.py
@@ -0,0 +1,290 @@
+from conan.tools.build.flags import architecture_flag, architecture_link_flag, libcxx_flags
+import os
+import textwrap
+from pathlib import Path
+
+from conan.tools.env.virtualbuildenv import VirtualBuildEnv
+from jinja2 import Template
+
+from conan.tools.build.cross_building import cross_building
+from conan.tools.files import save
+from conan.tools.microsoft.visual import VCVars
+from conan.tools.premake.premakedeps import PREMAKE_ROOT_FILE
+
+
+def _generate_flags(self, conanfile):
+    template = textwrap.dedent(
+        """\
+        {% if extra_cflags %}
+        -- C flags retrieved from CFLAGS environment, conan.conf(tools.build:cflags) and extra_cflags
+        filter { files { "**.c" } }
+            buildoptions { {{ extra_cflags }} }
+        filter {}
+        {% endif %}
+        {% if extra_cxxflags %}
+        -- CXX flags retrieved from CXXFLAGS environment, conan.conf(tools.build:cxxflags) and extra_cxxflags
+        filter { files { "**.cpp", "**.cxx", "**.cc" } }
+            buildoptions { {{ extra_cxxflags }} }
+        filter {}
+        {% endif %}
+        {% if extra_ldflags %}
+        -- Link flags retrieved from LDFLAGS environment, conan.conf(tools.build:sharedlinkflags), conan.conf(tools.build:exelinkflags) and extra_cxxflags
+        linkoptions { {{ extra_ldflags }} }
+        {% endif %}
+        {% if extra_defines %}
+        -- Defines retrieved from DEFINES environment, conan.conf(tools.build:defines) and extra_defines
+        defines { {{ extra_defines }} }
+        {% endif %}
+    """
+    )
+
+    def format_list(items):
+        return ", ".join(f'"{item}"' for item in items) if items else None
+
+    def to_list(value):
+        return value if isinstance(value, list) else [value] if value else []
+
+    arch_flags = to_list(architecture_flag(self._conanfile))
+    cxx_flags, libcxx_compile_definitions = libcxx_flags(self._conanfile)
+    arch_link_flags = to_list(architecture_link_flag(self._conanfile))
+
+    extra_defines = format_list(
+        conanfile.conf.get("tools.build:defines", default=[], check_type=list)
+        + self.extra_defines
+        + to_list(libcxx_compile_definitions)
+    )
+    extra_c_flags = format_list(
+        conanfile.conf.get("tools.build:cflags", default=[], check_type=list) + self.extra_cflags + arch_flags
+    )
+    extra_cxx_flags = format_list(
+        conanfile.conf.get("tools.build:cxxflags", default=[], check_type=list)
+        + to_list(cxx_flags)
+        + self.extra_cxxflags
+        + arch_flags
+    )
+    extra_ld_flags = format_list(
+        conanfile.conf.get("tools.build:sharedlinkflags", default=[], check_type=list)
+        + conanfile.conf.get("tools.build:exelinkflags", default=[], check_type=list)
+        + self.extra_ldflags
+        + arch_flags
+        + arch_link_flags
+    )
+
+    return (
+        Template(template, trim_blocks=True, lstrip_blocks=True)
+        .render(
+            extra_defines=extra_defines,
+            extra_cflags=extra_c_flags,
+            extra_cxxflags=extra_cxx_flags,
+            extra_ldflags=extra_ld_flags,
+        )
+        .strip()
+    )
+
+
+class _PremakeProject:
+    _premake_project_template = textwrap.dedent(
+        """\
+    project "{{ name }}"
+        {% if kind %}
+        kind "{{ kind }}"
+        {% endif %}
+        {% if flags %}
+    {{ flags | indent(indent_level, first=True) }}
+        {% endif %}
+    """
+    )
+
+    def __init__(self, name, conanfile) -> None:
+        self.name = name
+        self.kind = None
+        self.extra_cxxflags = []
+        self.extra_cflags = []
+        self.extra_ldflags = []
+        self.extra_defines = []
+        self.disable = False
+        self._conanfile = conanfile
+
+    def _generate(self):
+        """Generates project block"""
+        flags_content = _generate_flags(self, self._conanfile)  # Generate flags specific to this project
+        return Template(self._premake_project_template, trim_blocks=True, lstrip_blocks=True).render(
+            name=self.name,
+            kind="None" if self.disable else self.kind,
+            flags=flags_content,
+            indent_level=4,
+        )
+
+
+class PremakeToolchain:
+    """
+    PremakeToolchain generator
+    """
+
+    filename = "conantoolchain.premake5.lua"
+    # Keep template indented correctly for Lua output
+    _premake_file_template = textwrap.dedent(
+        """\
+    #!lua
+    -- Conan auto-generated toolchain file
+    {% if has_conan_deps %}
+    -- Include conandeps.premake5.lua with Conan dependency setup
+    include("conandeps.premake5.lua")
+    {% endif %}
+
+    -- Base build directory
+    local locationDir = path.normalize("{{ build_folder }}")
+
+    -- Generate workspace configurations
+    for wks in premake.global.eachWorkspace() do
+        workspace(wks.name)
+            -- Set base location for all workspaces
+            location(locationDir)
+            targetdir(path.join(locationDir, "bin"))
+            objdir(path.join(locationDir, "obj"))
+
+            {% if cppstd %}
+            cppdialect "{{ cppstd }}"
+            {% endif %}
+            {% if cstd %}
+            cdialect "{{ cstd }}"
+            {% endif %}
+            {% if shared != None %}
+            -- IMPORTANT: this global setting will only apply `project`s which do not have `kind` set.
+            -- IMPORTANT: This will not override existing `kind` set in `project` block.
+            -- To let conan take control over `kind` of the libraries, DO NOT SET `kind` (StaticLib or
+            -- SharedLib) in `project` block.
+            kind "{{ "SharedLib" if shared else "StaticLib" }}"
+            {% endif %}
+            {% if fpic != None %}
+            -- Enable position independent code
+            pic "{{ "On" if fpic else "Off" }}"
+            {% endif %}
+            filter { "architecture: not wasm64" }
+                -- TODO: There is an issue with premake and "wasm64" when system is declared "emscripten"
+                system "{{ target_build_os }}"
+            filter {}
+            {% if macho_to_amd64 %}
+            -- TODO: this should be fixed by premake: https://github.com/premake/premake-core/issues/2136
+            buildoptions "-arch x86_64"
+            linkoptions "-arch x86_64"
+            {% endif %}
+            {% if target_build_os == "emscripten" %}
+            filter { "system:emscripten", "kind:ConsoleApp or WindowedApp" }
+                -- Replace built in .wasm extension to .js to generate also a JavaScript files
+                targetextension ".js"
+            filter {}
+            {% endif %}
+            {% if flags %}
+    {{ flags | indent(indent_level, first=True) }}
+            {% endif %}
+
+            filter { "system:macosx" }
+                -- SHARED LIBS
+                -- In the future we could add an opt in configuration to run
+                -- fix_apple_shared_install_name on executables to have a similar behavior as CMake
+                -- generator. Premake does not allow adding absolute RCPATHS
+                -- Due to this limitation, if a consumer depends on a premake shared recipe, it will
+                -- require to run conanrun script to setup proper DYLD_LIBRARY_PATH
+                -- Reference: https://github.com/premake/premake-core/issues/2262#issuecomment-2378250385
+                linkoptions { "-Wl,-rpath,@loader_path" }
+            filter {}
+
+            conan_setup()
+    end
+
+        {% for project in projects.values() %}
+
+    {{ project._generate() }}
+        {% endfor %}
+    """
+    )
+
+    def __init__(self, conanfile):
+        """
+        :param conanfile: ``< ConanFile object >`` The current recipe object. Always use ``self``.
+        """
+        self._conanfile = conanfile
+        self._projects = {}
+        # Extra flags
+        #: List of extra ``CXX`` flags. Added to ``buildoptions``.
+        self.extra_cxxflags = []
+        #: List of extra ``C`` flags. Added to ``buildoptions``.
+        self.extra_cflags = []
+        #: List of extra linker flags. Added to ``linkoptions``.
+        self.extra_ldflags = []
+        #: List of extra preprocessor definitions. Added to ``defines``.
+        self.extra_defines = []
+
+    def project(self, project_name):
+        """
+        The returned object will also have the same properties as the workspace but will only affect
+        the project with the name.
+        :param project_name: The name of the project inside the workspace to be updated.
+        :return: ``<PremakeProject>`` object which allow to set project specific flags.
+        """
+        if project_name not in self._projects:
+            self._projects[project_name] = _PremakeProject(project_name, self._conanfile)
+        return self._projects[project_name]
+
+    def generate(self):
+        """
+        Creates a ``conantoolchain.premake5.lua`` file which will properly configure build paths,
+        binary paths, configuration settings and compiler/linker flags based on toolchain
+        configuration.
+        """
+        premake_conan_deps = Path(self._conanfile.generators_folder) / PREMAKE_ROOT_FILE
+        cppstd = self._conanfile.settings.get_safe("compiler.cppstd")
+        if cppstd:
+            # See premake possible cppstd values: https://premake.github.io/docs/cppdialect/
+            if cppstd.startswith("gnu"):
+                cppstd = f"gnu++{cppstd[3:]}"
+            elif cppstd[0].isnumeric():
+                cppstd = f"c++{cppstd}"
+
+        compilers_build_mapping = self._conanfile.conf.get(
+            "tools.build:compiler_executables", default={}, check_type=dict
+        )
+        if compilers_build_mapping:
+            build_env = VirtualBuildEnv(self._conanfile, auto_generate=False)
+            env = build_env.environment()
+            if "c" in compilers_build_mapping:
+                env.define("CC", compilers_build_mapping["c"])
+            if "cpp" in compilers_build_mapping:
+                env.define("CXX", compilers_build_mapping["cpp"])
+            build_env.generate()
+
+        macho_to_amd64 = (
+            self._conanfile.settings.arch
+            if cross_building(self._conanfile) and self._conanfile.settings.os == "Macos"
+            else None
+        )
+
+        content = Template(self._premake_file_template, trim_blocks=True, lstrip_blocks=True).render(
+            # Pass posix path for better cross-platform compatibility in Lua
+            build_folder=Path(self._conanfile.build_folder).as_posix(),
+            has_conan_deps=premake_conan_deps.exists(),
+            cppstd=cppstd,
+            cstd=self._conanfile.settings.get_safe("compiler.cstd"),
+            shared=self._conanfile.options.get_safe("shared"),
+            fpic=self._conanfile.options.get_safe("fPIC"),
+            target_build_os=self._target_build_os(),
+            macho_to_amd64=macho_to_amd64,
+            projects=self._projects,
+            flags=_generate_flags(self, self._conanfile),
+            indent_level=8,
+        )
+        save(
+            self,
+            os.path.join(self._conanfile.generators_folder, self.filename),
+            content,
+        )
+        # Generate VCVars if using MSVC
+        if "msvc" in self._conanfile.settings.compiler:
+            VCVars(self._conanfile).generate()
+
+    def _target_build_os(self):
+        conan_os = str(self._conanfile.settings.os)
+        if conan_os == "Macos":
+            return "macosx"
+        return conan_os.lower()


Test patch:
diff --git a/test/functional/toolchains/emscripten/test_emcc.py b/test/functional/toolchains/emscripten/test_emcc.py
index aee84ad07..f68646fdc 100644
--- a/test/functional/toolchains/emscripten/test_emcc.py
+++ b/test/functional/toolchains/emscripten/test_emcc.py
@@ -168,6 +168,29 @@ def test_autotools_emscripten():
     assert "Hello World Release!" in client.out
 
 
+@pytest.mark.tool("premake")
+@pytest.mark.tool("emcc")
+@pytest.mark.tool("node")
+@pytest.mark.skipif(sys.version_info < EMCC_MIN_PYTHON_VERSION, reason = "emcc requires Python 3.8 or higher")
+@pytest.mark.skipif(platform.system() != "Linux", reason = "Premake only installed in linux")
+def test_premake_emscripten():
+    client = TestClient()
+    client.run("new premake_exe -d name=hello -d version=0.1")
+    client.save({"wasm32": wasm32_profile, "asmjs": asmjs_profile, "base_emscripten_profile": base_emscripten_profile,})
+    client.run("build . -pr:h=wasm32")
+    assert "gmake --arch=wasm32" in client.out
+    assert os.path.exists(os.path.join(client.current_folder, "build-release", "bin", "hello.wasm"))
+    # Run JavaScript generated code which uses .wasm file
+    client.run_command("node ./build-release/bin/hello")
+    assert "Hello World Release!" in client.out
+    rmtree(os.path.join(client.current_folder, "build-release"))
+
+    client.run("build . -pr:h=asmjs")
+    assert "WASM=0" in client.out
+    # No wasm should have been generated for asm.js architecture
+    assert not os.path.exists(os.path.join(client.current_folder, "build-release", "bin", "hello.wasm"))
+    client.run_command("node ./build-release/bin/hello")
+    assert "Hello World Release!" in client.out
+
 # TODO: test_bazel_emscripten(): need WIP new bazel toolchain
 # TODO: test_msbuild_emscripten(): give support to msbuild
-# TODO: test_premake_emscripten(): premake tests
diff --git a/test/functional/toolchains/gnu/autotools/test_win_bash.py b/test/functional/toolchains/gnu/autotools/test_win_bash.py
index b46840171..d184e1a8d 100644
--- a/test/functional/toolchains/gnu/autotools/test_win_bash.py
+++ b/test/functional/toolchains/gnu/autotools/test_win_bash.py
@@ -130,7 +130,6 @@ def test_autotools_bash_complete_clang(frontend, runtime, build_type):
                  "main.cpp": main,
                  "profile_win": profile_win})
     client.run("build . -pr=profile_win")
-    print(client.out)
     client.run_command("main.exe")
     assert "__GNUC__" not in client.out
     assert "main __clang_major__18" in client.out
diff --git a/test/functional/toolchains/test_premake.py b/test/functional/toolchains/test_premake.py
index 5f87e3ede..02430d4ef 100644
--- a/test/functional/toolchains/test_premake.py
+++ b/test/functional/toolchains/test_premake.py
@@ -3,12 +3,13 @@ import textwrap
 
 import pytest
 
+from conan.test.utils.tools import TestClient
 from conan.test.assets.sources import gen_function_cpp
 
-
-@pytest.mark.skipif(platform.system() != "Linux", reason="Only for Linux now")
+@pytest.mark.skipif(platform.system() != "Linux", reason="Premake only installed on Linux CI machines")
+@pytest.mark.skipif(platform.machine() != "x86_64", reason="Premake Legacy generator only supports x86_64 machines")
 @pytest.mark.tool("premake")
-def test_premake(matrix_client):
+def test_premake_legacy(matrix_client):
     c = matrix_client
     conanfile = textwrap.dedent("""
         import os
@@ -72,3 +73,29 @@ def test_premake(matrix_client):
     c.run("build . -s build_type=Debug --build=missing")
     assert "main: Debug!" in c.out
     assert "matrix/1.0: Hello World Debug!" in c.out
+
+
+
+@pytest.mark.skipif(platform.system() != "Linux", reason="Only for Linux now")
+@pytest.mark.tool("premake")
+def test_premake_new_generator():
+    c = TestClient()
+    c.run("new premake_lib -d name=lib -d version=0.1 -o lib")
+    c.run("create lib")
+    c.run("new premake_exe -d name=example -d requires=lib/0.1 -d version=1.0 -o exe")
+    c.run("create exe")
+
+    assert "example/1.0 (test package): RUN: example" in c.out
+    assert "lib/0.1: Hello World Release!" in c.out
+    assert "example/1.0: Hello World Release!" in c.out
+
+
+@pytest.mark.skipif(platform.system() != "Linux", reason="Only for Linux now")
+@pytest.mark.tool("premake")
+def test_premake_shared_lib():
+    c = TestClient()
+    c.run("new premake_lib -d name=lib -d version=0.1 -o lib")
+    c.run("create lib -o '&:shared=True'")
+    assert "lib/0.1: package(): Packaged 1 '.so' file: liblib.so" in c.out
+    assert "lib/0.1: package(): Packaged 1 '.a' file: liblib.a" not in c.out
+
diff --git a/test/integration/toolchains/premake/test_premake.py b/test/integration/toolchains/premake/test_premake.py
index a7d8bce37..b6faeef54 100644
--- a/test/integration/toolchains/premake/test_premake.py
+++ b/test/integration/toolchains/premake/test_premake.py
@@ -2,8 +2,7 @@ import textwrap
 
 from conan.test.utils.tools import TestClient
 
-
-def test_premake_args():
+def test_premake_args_without_toolchain():
     c = TestClient()
     conanfile = textwrap.dedent("""
         from conan import ConanFile
@@ -21,4 +20,81 @@ def test_premake_args():
                 """)
     c.save({"conanfile.py": conanfile})
     c.run("build . -s compiler=msvc -s compiler.version=193 -s compiler.runtime=dynamic")
-    assert "conanfile.py: Running premake5 --file=myproject.lua vs2022 --myarg=myvalue!!" in c.out
+    assert 'conanfile.py: Running premake5 --file="myproject.lua" vs2022 --myarg=myvalue!!' in c.out
+
+def test_premake_build_without_toolchain():
+    tc = TestClient()
+    conanfile = textwrap.dedent(
+        """
+        from conan import ConanFile
+        from conan.tools.premake import Premake, PremakeToolchain
+
+        class Pkg(ConanFile):
+            settings = "os", "compiler", "build_type", "arch"
+            def run(self, cmd, *args, **kwargs):
+                self.output.info(f"Running {cmd}!!")
+            def build(self):
+                premake = Premake(self)
+                premake.configure()
+                premake.build(workspace="App")
+                """
+    )
+    tc.save({"conanfile.py": conanfile})
+    tc.run(
+        "build . -s compiler=msvc -s compiler.version=193 -s compiler.runtime=dynamic",
+        assert_error=True,
+    )
+    assert "Premake.build() method requires PrmakeToolchain to work properly" in tc.out
+
+
+def test_premake_build():
+    tc = TestClient()
+    conanfile = textwrap.dedent(
+        """
+        from conan import ConanFile
+        from conan.tools.premake import Premake, PremakeToolchain
+
+        class Pkg(ConanFile):
+            settings = "os", "compiler", "build_type", "arch"
+            generators = "PremakeToolchain"
+            def run(self, cmd, *args, **kwargs):
+                self.output.info(f"Running {cmd}!!")
+            def build(self):
+                premake = Premake(self)
+                premake.configure()
+                premake.build(workspace="App")
+                """
+    )
+    tc.save({"conanfile.py": conanfile})
+    tc.run(
+        "build . -s compiler=gcc -s compiler.version=13 -s compiler.libcxx=libstdc++ -s arch=x86_64 -c tools.build:jobs=4",
+    )
+    assert 'conanfile.premake5.lua" gmake --arch=x86_64!!' in tc.out
+    assert "Running make config=release all -j4!!" in tc.out
+
+
+def test_premake_build_with_targets():
+    tc = TestClient()
+    conanfile = textwrap.dedent(
+        """
+        from conan import ConanFile
+        from conan.tools.premake import Premake, PremakeToolchain
+
+        class Pkg(ConanFile):
+            settings = "os", "compiler", "build_type", "arch"
+            generators = "PremakeToolchain"
+            def run(self, cmd, *args, **kwargs):
+                self.output.info(f"Running {cmd}!!")
+            def build(self):
+                premake = Premake(self)
+                premake.configure()
+                premake.build(workspace="App", targets=["app", "test"])
+                """
+    )
+    tc.save({"conanfile.py": conanfile})
+    tc.run(
+        "build . -s compiler=gcc -s compiler.version=13 -s compiler.libcxx=libstdc++ -s arch=armv8 -c tools.build:jobs=42",
+    )
+    assert 'conanfile.premake5.lua" gmake --arch=arm64!!' in tc.out
+    assert "Running make config=release app test -j42!!" in tc.out
+
diff --git a/test/integration/toolchains/premake/test_premakedeps.py b/test/integration/toolchains/premake/test_premakedeps.py
index 5beafc197..9e42410b8 100644
--- a/test/integration/toolchains/premake/test_premakedeps.py
+++ b/test/integration/toolchains/premake/test_premakedeps.py
@@ -62,3 +62,4 @@ def test_premakedeps():
     # Assert package per configuration files
     assert_vars_file(client, 'debug')
     assert_vars_file(client, 'release')
+
diff --git a/test/integration/toolchains/premake/test_premaketoolchain.py b/test/integration/toolchains/premake/test_premaketoolchain.py
new file mode 100644
index 000000000..b6bdf0911
--- /dev/null
+++ b/test/integration/toolchains/premake/test_premaketoolchain.py
@@ -0,0 +1,153 @@
+import textwrap
+
+from conan.test.utils.tools import TestClient
+from conan.tools.premake.toolchain import PremakeToolchain
+
+
+def test_extra_flags_via_conf():
+    profile = textwrap.dedent(
+        """
+        [settings]
+        os=Linux
+        arch=x86_64
+        compiler=gcc
+        compiler.version=9
+        compiler.cppstd=17
+        compiler.libcxx=libstdc++
+        build_type=Release
+
+        [conf]
+        tools.build:cxxflags=["-flag1", "-flag2"]
+        tools.build:cflags=["-flag3", "-flag4"]
+        tools.build:sharedlinkflags+=["-flag5"]
+        tools.build:exelinkflags+=["-flag6"]
+        tools.build:defines=["define1=0"]
+    """
+    )
+    tc = TestClient()
+    tc.save(
+        {
+            "conanfile.txt": "[generators]\nPremakeToolchain",
+            "profile": profile,
+        }
+    )
+
+    tc.run("install . -pr:a=profile")
+    content = tc.load(PremakeToolchain.filename)
+    assert 'cppdialect "c++17"' in content
+
+    assert (
+        """
+        filter { files { "**.c" } }
+            buildoptions { "-flag3", "-flag4", "-m64" }
+        filter {}
+        """
+        in content
+    )
+
+    assert (
+        """
+        filter { files { "**.cpp", "**.cxx", "**.cc" } }
+            buildoptions { "-flag1", "-flag2", "-m64" }
+        filter {}
+        """
+        in content
+    )
+
+    assert 'linkoptions { "-flag5", "-flag6", "-m64" }' in content
+    assert 'defines { "define1=0", "_GLIBCXX_USE_CXX11_ABI=0" }' in content
+
+
+
+def test_project_configuration():
+    tc = TestClient(path_with_spaces=False)
+    profile = textwrap.dedent(
+        """
+        [settings]
+        arch=armv8
+        build_type=Release
+        compiler=apple-clang
+        compiler.cppstd=gnu17
+        compiler.cstd=gnu11
+        compiler.libcxx=libc++
+        compiler.version=17
+        os=Macos
+    """
+    )
+    conanfile = textwrap.dedent(
+        """
+        from conan import ConanFile
+        from conan.tools.layout import basic_layout
+        from conan.tools.premake import Premake, PremakeDeps, PremakeToolchain
+        import os
+
+        class Pkg(ConanFile):
+            settings = "os", "compiler", "build_type", "arch"
+            name = "pkg"
+            version = "1.0"
+            options = {"fPIC": [True, False]}
+            default_options = {"fPIC": True}
+
+            def layout(self):
+                basic_layout(self, src_folder="src")
+
+            def generate(self):
+                tc = PremakeToolchain(self)
+                tc.extra_defines = ["VALUE=2"]
+                tc.extra_cflags = ["-Wextra"]
+                tc.extra_cxxflags = ["-Wall", "-Wextra"]
+                tc.extra_ldflags = ["-lm"]
+                tc.project("main").extra_defines = ["TEST=False"]
+                tc.project("test").disable = True
+                tc.project("main").extra_cxxflags = ["-FS"]
+                tc.generate()
+    """
+    )
+    tc.save({"conanfile.py": conanfile, "profile": profile})
+    tc.run("install . -pr:a profile")
+
+    toolchain = tc.load("build-release/conan/conantoolchain.premake5.lua")
+
+    assert 'pic "On"' in toolchain
+    assert (
+        """
+        filter { files { "**.c" } }
+            buildoptions { "-Wextra" }
+        filter {}
+        """
+        in toolchain
+    )
+    assert (
+        """
+        filter { files { "**.cpp", "**.cxx", "**.cc" } }
+            buildoptions { "-stdlib=libc++", "-Wall", "-Wextra" }
+        filter {}
+        """
+        in toolchain
+    )
+    assert 'linkoptions { "-lm" }' in toolchain
+    assert 'defines { "VALUE=2" }' in toolchain
+    assert 'linkoptions { "-Wl,-rpath,@loader_path" }' in toolchain
+    assert (
+        textwrap.dedent(
+            """
+    project "main"
+        -- CXX flags retrieved from CXXFLAGS environment, conan.conf(tools.build:cxxflags) and extra_cxxflags
+        filter { files { "**.cpp", "**.cxx", "**.cc" } }
+            buildoptions { "-stdlib=libc++", "-FS" }
+        filter {}
+        -- Defines retrieved from DEFINES environment, conan.conf(tools.build:defines) and extra_defines
+        defines { "TEST=False" }
+        """
+        )
+        in toolchain
+    )
+    assert (
+        textwrap.dedent(
+            """
+        project "test"
+            kind "None"
+        """
+        )
+        in toolchain
+    )