premake / premake-core

Premake
https://premake.github.io/
BSD 3-Clause "New" or "Revised" License
3.24k stars 617 forks source link

Support files at solution scope #1061

Open TurkeyMan opened 6 years ago

TurkeyMan commented 6 years ago

Support adding files at solution scope (in VS). No build action is performed on such a file, and as such, this shouldn't affect gmake/etc.

Useful for .editorconfig, .natvis files, etc.

erincatto commented 6 years ago

This page describes the process for adding natvis files manually in Visual Studio: https://msdn.microsoft.com/en-us/library/jj620914.aspx

tdesveauxPKFX commented 6 years ago

This works and add both files to each projects.

workspace "Solution"

    configurations { "Release", "Debug" }
    platforms { "x64", "x32" }

    files {
        "test.natvis",
        "config.editorconfig"
    }

    project "Project"

        kind        "ConsoleApp"
        language    "C++"
        uuid        "00000000-0000-0000-0000-000000000000"

        files { "project/main.c" }

    project "otherProject"

        kind        "ConsoleApp"
        language    "C++"
        uuid        "00000000-0000-0000-0000-000000000001"

        files { "otherProject/main2.c" }

If you encounter an issue with this, can you post an example that does not work as expected?

erincatto commented 6 years ago

Yes, that is what I have been doing. Visual Studio also supports adding files at the solution scope. The page I linked above shows the process for adding a natvis file at solution scope and you will see those files are listed in the .sln text in a "Solution Item" folder. This is a nicer way to display these files that have solution level scope.

tdesveauxPKFX commented 6 years ago

Indeed, that would be useful.

Do you know if natvis informations from a solution level file are added to the pdb when building a c++ project as they are when the file is at project level?

I quickly looked into it and this is what Visual Studio expect:

Project("{00000000-0000-0000-0000-000000000000}") = "SolutionFolderName", "SolutionFolderName", "{00000000-0000-0000-0000-000000000000}"
    ProjectSection(SolutionItems) = preProject
        PathTo\file.natvis = PathTo\file.natvis
    EndProjectSection
EndProject

SolutionFolderName can either be a solution folder or a project. However, if it is a project, the file will appear in the next solution folder found for some reason.

The ProjectSection block must be in a Project block, so you can't have a file at solution root.

There might be more to it but this would be a good start I think.

Implementation should be here.

Baking process should be modified too I assume but I am not familiar with this part.

tvandijck commented 6 years ago

this might not be so easy using the standard files api, since those are automatically merged down into the project and configs... To support this, we might have to add a new workspacefiles api or something like that, that is explicitly bound to the workspace scope.

the alternative would be to create a dummy project like this:

project 'workspaceitems'
     kind 'workspaceitems'

    files { '**.natvis' }

that would at least isolate the files, or you could even make a new container type.

solutionitems 'foobar'
     files { '**.natvis' }

that would in turn allow us to limit the api's callable inside of that scope. The best part about this particular solution, is that you can add files to the solutionitems at any time, without having to manually exit the project scope and go back into the workspace scope, using the project(nil) pattern. And, the 'name' of the solutionitems container could basically be the folder to put the files in.

samsinsane commented 6 years ago

I like the idea of a new API, workspacefiles is perfect in my eyes. I would like to be able to specify a workspace file from a project, "this project adds these workspace files" - at work, we have multiple teams consuming each others libraries, and having to manually maintain the "workspace files" at the workspace level would be awful.

starkos commented 6 years ago

Curious…if you could specify the workspace level files using files(), maybe like (addressing the comment from @samsinsane):

project "MyProject"
    files { ... } -- project level files

project "*"  -- selects workspace scope, if you haven't seen it before
    files { ... } -- workspace level files

…would that be preferable to workspacefiles()? I kind of feel like it would be, even though it would be a PITA the way to code is now.

samsinsane commented 6 years ago

@starkos I have not seen that before, that's a thing? That's pretty strange but cool, and I'd rather use that than expect someone to figure out how to get workspacefiles to work. Thanks!

tvandijck commented 6 years ago

The problem with:

project "*"  -- selects workspace scope, if you haven't seen it before
    files { ... } 

is that while it adds the file at workspace scope, baking puts them simply in the project anyway. This is a supported workflow, and it's used.. So if you wanted to support "workspace" specific files using that API, you would break something that currently allows you to add a file to all projects. You can't have both.

If you want both, you need a new API...

personally, I like the

solutionitems 'foobar'
     files { '**.natvis' }

the best, over a single workspacefiles API. You can make it show up as a separate folder "foobar", containing the files you selected, and the vpaths API could work inside that container too, allowing further categorization of those files.

starkos commented 6 years ago

The problem with ... is that while it adds the file at workspace scope, baking puts them simply in the project anyway.

That's right. I was just asking, for academic purposes, if it was possible, which would people prefer.

TurkeyMan commented 6 years ago

I'm motivated by both angles... what are some use cases for regular files calls at workspace scope? I am tilted slightly in favour of "make files at workspace scope just work if the action can express it" rather than adding a new API. I think intuition would have basically anyone call files at workspace scope. What are some legit reasons people call files at workspace scope currently? I've never done it in any script I've ever written...

TurkeyMan commented 6 years ago

I totally understand Tom's argument, but I wonder if the use case we'd be sacrificing is worth the subversion of intuition. Which is more useful?

erincatto commented 6 years ago

I found some other use cases that would make this nice to have: Doxyfile, .gitignore, premake5.lua, etc. This seems like a generally useful feature.

starkos commented 6 years ago

I think @TurkeyMan is asking if there is any use case where you would call files() at the workspace scope, but not want those files to appear in the workspace if the tool supports it. I can't think of any, myself.

tdesveauxPKFX commented 6 years ago

I can't think of any either.

I do like the solutionitems container proposed by @tvandijck, this would leave the possibility to add files at workspace scope that are inherited by projects. Or we could use group?

tvandijck commented 6 years ago

I'm concerned about separating the files() call at workspace scope. For one, it would require special casing that API in the context code. But more importantly, you would have to introduce a secondairy API to allow grouping. In projects we have vpath to allow grouping of files in folders, but at the workspace scope you can't do that, because that would then require you to also special case the vpaths API, which you can't do, because that is 100% certainly an API that is used at workspace scope but is assumed to apply to all projects. (in Heroes of the Storms & Starcraft2 for sure, because I wrote that).

So personally,

I still think a 'workspaceitems' container would be the most elegant.

workspaceitems 'foobar'
    files { '**.lua' }
    vpaths { ['Premake Scripts/*'] = '**.lua' }
TurkeyMan commented 6 years ago

Yeah, I'm feeling Tom's suggestion. making a distinct container for workspace items feels okay. What are the semantics of that API? Is it mutually exclusive with project? Does calling workspaceitems or project cancel the other out?

andrewbhamilton commented 6 years ago

+1

fsfod commented 6 years ago

Heres a premake override you can paste in to your premake5.lua file for anyone who still want this.

require('vstudio')
premake.api.register {
  name = "workspace_files",
  scope = "workspace",
  kind = "list:string",
}

premake.override(premake.vstudio.sln2005, "projects", function(base, wks)
  if wks.workspace_files and #wks.workspace_files > 0 then
    premake.push('Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{' .. os.uuid("Solution Items:"..wks.name) .. '}"')
    premake.push("ProjectSection(SolutionItems) = preProject")
    for _, file in ipairs(wks.workspace_files) do
      file = path.rebase(file, ".", wks.location)
      premake.w(file.." = "..file)
    end
    premake.pop("EndProjectSection")
    premake.pop("EndProject")
  end
  base(wks)
end)

You can just use it like this

workspace "test"
    workspace_files {
        ".editorconfig",
    }
nikitabuyevich commented 4 years ago

Expanded @fsfod 's solution to include n number of solution folders for n number of files.

require('vstudio')
premake.api.register {
  name = "solutionitems",
  scope = "workspace",
  kind = "list:keyed:list:string",
}

premake.override(premake.vstudio.sln2005, "projects", function(base, wks)
    for _, folder in ipairs(wks.solutionitems) do
      for name, files in pairs(folder) do
        premake.push('Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "'..name..'", "'..name..'", "{' .. os.uuid("Solution Items:"..wks.name) .. '}"')
        premake.push("ProjectSection(SolutionItems) = preProject")
        for _, file in ipairs(files) do
          file = path.rebase(file, ".", wks.location)
          premake.w(file.." = "..file)
        end
        premake.pop("EndProjectSection")
        premake.pop("EndProject")
      end
    end
  base(wks)
end)

You can just use it like this

  solutionitems {
    { ["doc"] = { 
        "README.md",
        "SomeOther.md",
      } 
    },
    { ["foobar"] = { 
        "hello.md",
      } 
    },
    { ["stuff"] = { 
        "1.md",
        "2.md",
        "3.md",
      } 
    },
  }
vikhik commented 2 years ago

Did this get anywhere? Would love to have my natvis added to my premake generated .sln

nickclark2016 commented 2 years ago

I don't think this has gone anywhere. You should able to apply natvis at the project scope, though.

vikhik commented 2 years ago

For the next person. I couldn't get the above snippets working with latest (I'm also relatively new to modern premake).

But this does work, at the project scope, and keeps generating the vs filters properly for other source files (e.g. my natvis are in /Third-Party/** and my other projects are in /%{prj.name}/Source/**


function usesMyLib()
    language "C++"
    files { "%{prj.name}/**.h", "%{prj.name}/**.c", "%{prj.name}/**.cpp", "**.natvis" }
    local projname = project().name
    vpaths { 
        ["__Natvis"] = "**.natvis",
        ["*"] = projname.."/Source/**"
    }
    includedirs {"./MyLib/Public" }
end```
p0358 commented 1 year ago

As I didn't really like either of the solutions above, I decided to take my own stab at this :)

Long but worth it, I think the end result is the most intuitive.

require("vstudio")
premake.api.register {
    name = "solutionitems",
    scope = "workspace",
    kind = "table",
}
function recursiveSolutionItemsProject(wks, tbl)
    for _, file in pairs(tbl) do
        if type(_) ~= "number" then

            local name = _
            local tbl = file
            premake.push('Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "'..name..'", "'..name..'", "{' .. os.uuid("Solution Items:"..wks.name..":"..name) .. '}"')
            premake.push("ProjectSection(SolutionItems) = preProject")

            local children = false

            for _, file in pairs(file) do
                if type(_) == "number" and type(file) == "string" then
                    for _, file in ipairs(os.matchfiles(file)) do
                        file = path.rebase(file, ".", wks.location)
                        premake.w(file.." = "..file)
                    end
                end

                if type(_) ~= "number" then
                    children = true
                end
            end

            premake.pop("EndProjectSection")
            premake.pop("EndProject")

            if children then
                recursiveSolutionItemsProject(wks, tbl)
            end
        end
    end
end
premake.override(premake.vstudio.sln2005, "projects", function(base, wks)
    if wks.solutionitems and #wks.solutionitems > 0 then
        -- root level
        premake.push('Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{' .. os.uuid("Solution Items:"..wks.name) .. '}"')
        premake.push("ProjectSection(SolutionItems) = preProject")
        for _, file in pairs(wks.solutionitems) do
            if type(_) == "number" and type(file) == "string" then
                for _, file in ipairs(os.matchfiles(file)) do
                    file = path.rebase(file, ".", wks.location)
                    premake.w(file.." = "..file)
                end
            end
        end
        premake.pop("EndProjectSection")
        premake.pop("EndProject")
        -- subprojects
        recursiveSolutionItemsProject(wks, wks.solutionitems)
    end
    base(wks)
end)
function recursiveSolutionItemsNestedProjectLines(wks, parent, tbl)
    local parent_uuid
    if parent == "Solution Items" then
        parent_uuid = os.uuid("Solution Items:"..wks.name)
    else
        parent_uuid = os.uuid("Solution Items:"..wks.name..":"..parent)
    end

    for _, file in pairs(tbl) do
        if type(_) ~= "number" then
            local name = _
            local new_tbl = file
            project_uuid = os.uuid("Solution Items:"..wks.name..":"..name)
            premake.w("{%s} = {%s}", project_uuid, parent_uuid)
            --print("child:", name, "parent:", parent)
            recursiveSolutionItemsNestedProjectLines(wks, name, new_tbl)
        end
    end
end
premake.override(premake.vstudio.sln2005, "nestedProjects", function(base, wks)
    premake.push("GlobalSection(NestedProjects) = preSolution")
    -- base copy-paste START
    local tr = premake.workspace.grouptree(wks)
    if premake.tree.hasbranches(tr) then
        premake.tree.traverse(tr, {
            onnode = function(n)
                if n.parent.uuid then
                    premake.w("{%s} = {%s}", (n.project or n).uuid, n.parent.uuid)
                end
            end
        })
    end
    -- base copy-paste END
    -- our START
    parent = "Solution Items"
    recursiveSolutionItemsNestedProjectLines(wks, parent, wks.solutionitems)
    -- our END
    premake.pop("EndGlobalSection")
end)

Usage:

workspace "launcher"
solutionitems {
    "./README.md",
    ["GitHub"] = {
        "./.github/dependabot.yml",
        ["workflows"] = {
            "./.github/workflows/**.yml",
        }
    },
    ["Premake"] = {
        "./premake5.lua",
        ["Deps"] = {
            "./deps/premake/**.lua"
        }
    },
}

Effect: image

PS: don't listen to the person above me, both of the previous snippets still work on premake5 (well on 5.0.0-beta2 at least...)

SiriusED commented 1 year ago

@p0358 Ye, the code looks a bit huge but it works exactly like I need. Thank You for 100% work code, it's rare when you can just copy the code and it works with zero changes and hours of edition :) And btw, if someone like me likes when main premake5.lua script has minimum of code lines you can simply paste all this code inside external script and then just include("script.lua") and problem solved.