1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
|
--!A cross-platform build utility based on Lua
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Copyright (C) 2015-present, Xmake Open Source Community.
--
-- @author ruki
-- @file template.lua
--
-- imports
import("core.base.global")
import("core.base.hashset")
import("core.language.language")
import("core.package.addon")
-- some builtin template variables in xmake.lua
function builtinvars(targetname)
return {TARGET_NAME = targetname,
FAQ = function() return io.readfile(path.join(os.programdir(), "scripts", "faq.lua")) end}
end
-- get all template roots with extra meta information
--
-- priority:
-- 1. addon: <globaldir>/addons/<name>/<version>/templates
-- 2. global: <globaldir>/templates
-- 3. builtin: <programdir>/templates
function rootinfos()
local results = {}
-- get template directories from the installed addons
for _, addoninfo in ipairs(addon.payloadinfos("templates")) do
if os.isdir(addoninfo.dir) then
table.insert(results, {kind = "addon", name = addoninfo.name, version = addoninfo.version, dir = addoninfo.dir})
end
end
-- get template directories from global and builtin
local dir = path.join(global.directory(), "templates")
if os.isdir(dir) then
table.insert(results, {kind = "global", dir = dir})
end
dir = path.join(os.programdir(), "templates")
if os.isdir(dir) then
table.insert(results, {kind = "builtin", dir = dir})
end
return results
end
-- get the root information of the given template directory
--
-- @param templatedir the template directory, e.g. <programdir>/templates/c/console
-- @return the root information, @see rootinfos
--
function rootinfo_of(templatedir)
if not templatedir then
return
end
templatedir = path.absolute(templatedir)
for _, info in ipairs(rootinfos()) do
local rootdir = path.absolute(info.dir)
if templatedir == rootdir or templatedir:startswith(rootdir .. path.sep()) then
return info
end
end
end
-- get template root directories
function rootdirs()
local results = {}
for _, info in ipairs(rootinfos()) do
table.insert(results, info.dir)
end
return results
end
-- get template root directory
function _templateid_subdir(templateid)
local items = templateid:split(".", {plain = true})
if not items or #items == 0 then
return
end
for _, item in ipairs(items) do
if #item == 0 then
return
end
end
return path.join(table.unpack(items))
end
function templatedir(lang, templateid)
assert(lang)
assert(templateid)
-- the qualified template of an addon, e.g. @addon/basic-templates/verilator.console
--
-- @note the template ids are not namespaced, we only need it to disambiguate the conflicts
--
if templateid:startswith("@addon/") then
local referenceinfo = addon.resolve_reference(templateid, "/", "templates")
local subdir = _templateid_subdir(referenceinfo.name)
if subdir then
local dir = path.join(referenceinfo.dir, lang, subdir)
if os.isfile(path.join(dir, "xmake.lua")) then
return dir
end
end
return
end
for _, rootdir in ipairs(rootdirs()) do
local subdir = _templateid_subdir(templateid)
if subdir then
local dir = path.join(rootdir, lang, subdir)
if os.isfile(path.join(dir, "xmake.lua")) then
return dir
end
end
end
end
-- copy template files to project directory
function copy_files(sourcedir, projectdir)
local createdfiles = {}
for _, srcfile in ipairs(os.files(path.join(sourcedir, "**"))) do
local relpath = path.relative(srcfile, sourcedir)
local dstfile = path.absolute(path.join(projectdir, relpath))
os.mkdir(path.directory(dstfile))
os.cp(srcfile, dstfile, {writeable = true})
table.insert(createdfiles, dstfile)
end
return createdfiles
end
-- only replace variables in template source files and xmake.lua
function _need_replace_variables(filepath)
if not os.isfile(filepath) then
return false
end
if path.filename(filepath):lower() == "xmake.lua" then
return true
end
local extension = path.extension(filepath)
if extension then
return language.extensions()[extension:lower()] ~= nil
end
end
-- replace variables in files
function replace_variables_in_files(files, vars)
local pattern = "%${(.-)}"
for _, file in ipairs(files) do
if _need_replace_variables(file) then
io.gsub(file, "(" .. pattern .. ")", function(_, variable)
variable = variable:trim()
local value = vars[variable]
if value == nil then
return "${" .. variable .. "}"
end
return type(value) == "function" and value() or tostring(value)
end, {encoding = "binary"})
end
end
end
-- get all languages from templates
function languages()
local found = hashset.new()
for _, rootdir in ipairs(rootdirs()) do
local languages_dirs = os.dirs(path.join(rootdir, "*"))
if languages_dirs then
for _, d in ipairs(languages_dirs) do
found:insert(path.filename(d))
end
end
end
local results = found:to_array()
table.sort(results)
return results
end
-- get the reference of the given template, e.g. @addon/basic-templates/verilator.console
function _template_reference(rootinfo, templateid)
if rootinfo.kind == "addon" then
return string.format("@addon/%s/%s", rootinfo.name, templateid)
end
return path.join(rootinfo.dir, templateid)
end
-- get all templates for the given language
function templates(lang)
assert(lang)
local found = hashset.new()
local providers = {}
for _, rootinfo in ipairs(rootinfos()) do
local rootdir = rootinfo.dir
local templateroot = path.join(rootdir, lang)
local configfiles = os.files(path.join(templateroot, "**", "xmake.lua"))
if configfiles then
local templatedirs = {}
for _, configfile in ipairs(configfiles) do
local templatedir = path.directory(configfile)
local relpath = path.relative(templatedir, templateroot)
if relpath and relpath ~= "." then
table.insert(templatedirs, {dir = templatedir, relpath = relpath})
end
end
table.sort(templatedirs, function(a, b) return #a.relpath < #b.relpath end)
local accepted = {}
for _, item in ipairs(templatedirs) do
local ok = true
for _, root in ipairs(accepted) do
if item.dir:startswith(root .. path.sep()) then
ok = false
break
end
end
if ok then
table.insert(accepted, item.dir)
local templateid = (item.relpath:gsub("[/\\]", "."))
-- the templates are not namespaced, so we need to report the conflicts of the addons,
-- otherwise we do not know which template will be used
local provider = providers[templateid]
if provider and (provider.kind == "addon" or rootinfo.kind == "addon") then
wprint("template(%s/%s) conflicts, we will use the first one!\n -> %s\n -> %s\nplease use the qualified template id to disambiguate them.",
lang, templateid, _template_reference(provider, templateid), _template_reference(rootinfo, templateid))
else
providers[templateid] = rootinfo
found:insert(templateid)
end
end
end
end
end
local results = found:to_array()
table.sort(results)
return results
end
-- get languages for the given template
function languages_for_template(templateid)
local accepted = {}
for _, lang in ipairs(languages()) do
if templatedir(lang, templateid) then
table.insert(accepted, lang)
end
end
return accepted
end
-- get templates with all supported languages
function templates_with_languages()
local templates_map = {}
for _, lang in ipairs(languages()) do
for _, name in ipairs(templates(lang)) do
templates_map[name] = templates_map[name] or {}
if not table.contains(templates_map[name], lang) then
table.insert(templates_map[name], lang)
end
end
end
return templates_map
end
|