makevars.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. // Copyright 2016 Google Inc. All rights reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package android
  15. import (
  16. "bytes"
  17. "fmt"
  18. "path/filepath"
  19. "runtime"
  20. "sort"
  21. "strings"
  22. "github.com/google/blueprint"
  23. "github.com/google/blueprint/pathtools"
  24. "github.com/google/blueprint/proptools"
  25. )
  26. func init() {
  27. RegisterMakeVarsProvider(pctx, androidMakeVarsProvider)
  28. }
  29. func androidMakeVarsProvider(ctx MakeVarsContext) {
  30. ctx.Strict("MIN_SUPPORTED_SDK_VERSION", ctx.Config().MinSupportedSdkVersion().String())
  31. }
  32. // /////////////////////////////////////////////////////////////////////////////
  33. // BaseMakeVarsContext contains the common functions for other packages to use
  34. // to declare make variables
  35. type BaseMakeVarsContext interface {
  36. Config() Config
  37. DeviceConfig() DeviceConfig
  38. AddNinjaFileDeps(deps ...string)
  39. Failed() bool
  40. // These are equivalent to Strict and Check, but do not attempt to
  41. // evaluate the values before writing them to the Makefile. They can
  42. // be used when all ninja variables have already been evaluated through
  43. // Eval().
  44. StrictRaw(name, value string)
  45. CheckRaw(name, value string)
  46. // GlobWithDeps returns a list of files that match the specified pattern but do not match any
  47. // of the patterns in excludes. It also adds efficient dependencies to rerun the primary
  48. // builder whenever a file matching the pattern as added or removed, without rerunning if a
  49. // file that does not match the pattern is added to a searched directory.
  50. GlobWithDeps(pattern string, excludes []string) ([]string, error)
  51. // Phony creates a phony rule in Make, which will allow additional DistForGoal
  52. // dependencies to be added to it. Phony can be called on the same name multiple
  53. // times to add additional dependencies.
  54. Phony(names string, deps ...Path)
  55. // DistForGoal creates a rule to copy one or more Paths to the artifacts
  56. // directory on the build server when the specified goal is built.
  57. DistForGoal(goal string, paths ...Path)
  58. // DistForGoalWithFilename creates a rule to copy a Path to the artifacts
  59. // directory on the build server with the given filename when the specified
  60. // goal is built.
  61. DistForGoalWithFilename(goal string, path Path, filename string)
  62. // DistForGoals creates a rule to copy one or more Paths to the artifacts
  63. // directory on the build server when any of the specified goals are built.
  64. DistForGoals(goals []string, paths ...Path)
  65. // DistForGoalsWithFilename creates a rule to copy a Path to the artifacts
  66. // directory on the build server with the given filename when any of the
  67. // specified goals are built.
  68. DistForGoalsWithFilename(goals []string, path Path, filename string)
  69. }
  70. // MakeVarsContext contains the set of functions available for MakeVarsProvider
  71. // and SingletonMakeVarsProvider implementations.
  72. type MakeVarsContext interface {
  73. BaseMakeVarsContext
  74. ModuleName(module blueprint.Module) string
  75. ModuleDir(module blueprint.Module) string
  76. ModuleSubDir(module blueprint.Module) string
  77. ModuleType(module blueprint.Module) string
  78. ModuleProvider(module blueprint.Module, key blueprint.ProviderKey) interface{}
  79. BlueprintFile(module blueprint.Module) string
  80. ModuleErrorf(module blueprint.Module, format string, args ...interface{})
  81. Errorf(format string, args ...interface{})
  82. VisitAllModules(visit func(Module))
  83. VisitAllModulesIf(pred func(Module) bool, visit func(Module))
  84. // Verify the make variable matches the Soong version, fail the build
  85. // if it does not. If the make variable is empty, just set it.
  86. Strict(name, ninjaStr string)
  87. // Check to see if the make variable matches the Soong version, warn if
  88. // it does not. If the make variable is empty, just set it.
  89. Check(name, ninjaStr string)
  90. // These are equivalent to the above, but sort the make and soong
  91. // variables before comparing them. They also show the unique entries
  92. // in each list when displaying the difference, instead of the entire
  93. // string.
  94. StrictSorted(name, ninjaStr string)
  95. CheckSorted(name, ninjaStr string)
  96. // Evaluates a ninja string and returns the result. Used if more
  97. // complicated modification needs to happen before giving it to Make.
  98. Eval(ninjaStr string) (string, error)
  99. }
  100. // MakeVarsModuleContext contains the set of functions available for modules
  101. // implementing the ModuleMakeVarsProvider interface.
  102. type MakeVarsModuleContext interface {
  103. BaseMakeVarsContext
  104. }
  105. var _ PathContext = MakeVarsContext(nil)
  106. type MakeVarsProvider func(ctx MakeVarsContext)
  107. func RegisterMakeVarsProvider(pctx PackageContext, provider MakeVarsProvider) {
  108. makeVarsInitProviders = append(makeVarsInitProviders, makeVarsProvider{pctx, provider})
  109. }
  110. // SingletonMakeVarsProvider is a Singleton with an extra method to provide extra values to be exported to Make.
  111. type SingletonMakeVarsProvider interface {
  112. // MakeVars uses a MakeVarsContext to provide extra values to be exported to Make.
  113. MakeVars(ctx MakeVarsContext)
  114. }
  115. var singletonMakeVarsProvidersKey = NewOnceKey("singletonMakeVarsProvidersKey")
  116. func getSingletonMakevarsProviders(config Config) *[]makeVarsProvider {
  117. return config.Once(singletonMakeVarsProvidersKey, func() interface{} {
  118. return &[]makeVarsProvider{}
  119. }).(*[]makeVarsProvider)
  120. }
  121. // registerSingletonMakeVarsProvider adds a singleton that implements SingletonMakeVarsProvider to
  122. // the list of MakeVarsProviders to run.
  123. func registerSingletonMakeVarsProvider(config Config, singleton SingletonMakeVarsProvider) {
  124. // Singletons are registered on the Context and may be different between different Contexts,
  125. // for example when running multiple tests. Store the SingletonMakeVarsProviders in the
  126. // Config so they are attached to the Context.
  127. singletonMakeVarsProviders := getSingletonMakevarsProviders(config)
  128. *singletonMakeVarsProviders = append(*singletonMakeVarsProviders,
  129. makeVarsProvider{pctx, singletonMakeVarsProviderAdapter(singleton)})
  130. }
  131. // singletonMakeVarsProviderAdapter converts a SingletonMakeVarsProvider to a MakeVarsProvider.
  132. func singletonMakeVarsProviderAdapter(singleton SingletonMakeVarsProvider) MakeVarsProvider {
  133. return func(ctx MakeVarsContext) { singleton.MakeVars(ctx) }
  134. }
  135. // ModuleMakeVarsProvider is a Module with an extra method to provide extra values to be exported to Make.
  136. type ModuleMakeVarsProvider interface {
  137. Module
  138. // MakeVars uses a MakeVarsModuleContext to provide extra values to be exported to Make.
  139. MakeVars(ctx MakeVarsModuleContext)
  140. }
  141. // /////////////////////////////////////////////////////////////////////////////
  142. func makeVarsSingletonFunc() Singleton {
  143. return &makeVarsSingleton{}
  144. }
  145. type makeVarsSingleton struct {
  146. varsForTesting []makeVarsVariable
  147. installsForTesting []byte
  148. }
  149. type makeVarsProvider struct {
  150. pctx PackageContext
  151. call MakeVarsProvider
  152. }
  153. // Collection of makevars providers that are registered in init() methods.
  154. var makeVarsInitProviders []makeVarsProvider
  155. type makeVarsContext struct {
  156. SingletonContext
  157. config Config
  158. pctx PackageContext
  159. vars []makeVarsVariable
  160. phonies []phony
  161. dists []dist
  162. }
  163. var _ MakeVarsContext = &makeVarsContext{}
  164. type makeVarsVariable struct {
  165. name string
  166. value string
  167. sort bool
  168. strict bool
  169. }
  170. type phony struct {
  171. name string
  172. deps []string
  173. }
  174. type dist struct {
  175. goals []string
  176. paths []string
  177. }
  178. func (s *makeVarsSingleton) GenerateBuildActions(ctx SingletonContext) {
  179. if !ctx.Config().KatiEnabled() {
  180. return
  181. }
  182. outFile := absolutePath(PathForOutput(ctx,
  183. "make_vars"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
  184. lateOutFile := absolutePath(PathForOutput(ctx,
  185. "late"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
  186. installsFile := absolutePath(PathForOutput(ctx,
  187. "installs"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
  188. if ctx.Failed() {
  189. return
  190. }
  191. var vars []makeVarsVariable
  192. var dists []dist
  193. var phonies []phony
  194. var katiInstalls []katiInstall
  195. var katiSymlinks []katiInstall
  196. providers := append([]makeVarsProvider(nil), makeVarsInitProviders...)
  197. providers = append(providers, *getSingletonMakevarsProviders(ctx.Config())...)
  198. for _, provider := range providers {
  199. mctx := &makeVarsContext{
  200. SingletonContext: ctx,
  201. pctx: provider.pctx,
  202. }
  203. provider.call(mctx)
  204. vars = append(vars, mctx.vars...)
  205. phonies = append(phonies, mctx.phonies...)
  206. dists = append(dists, mctx.dists...)
  207. }
  208. ctx.VisitAllModules(func(m Module) {
  209. if provider, ok := m.(ModuleMakeVarsProvider); ok && m.Enabled() {
  210. mctx := &makeVarsContext{
  211. SingletonContext: ctx,
  212. }
  213. provider.MakeVars(mctx)
  214. vars = append(vars, mctx.vars...)
  215. phonies = append(phonies, mctx.phonies...)
  216. dists = append(dists, mctx.dists...)
  217. }
  218. if m.ExportedToMake() {
  219. katiInstalls = append(katiInstalls, m.base().katiInstalls...)
  220. katiSymlinks = append(katiSymlinks, m.base().katiSymlinks...)
  221. }
  222. })
  223. if ctx.Failed() {
  224. return
  225. }
  226. sort.Slice(vars, func(i, j int) bool {
  227. return vars[i].name < vars[j].name
  228. })
  229. sort.Slice(phonies, func(i, j int) bool {
  230. return phonies[i].name < phonies[j].name
  231. })
  232. lessArr := func(a, b []string) bool {
  233. if len(a) == len(b) {
  234. for i := range a {
  235. if a[i] < b[i] {
  236. return true
  237. }
  238. }
  239. return false
  240. }
  241. return len(a) < len(b)
  242. }
  243. sort.Slice(dists, func(i, j int) bool {
  244. return lessArr(dists[i].goals, dists[j].goals) || lessArr(dists[i].paths, dists[j].paths)
  245. })
  246. outBytes := s.writeVars(vars)
  247. if err := pathtools.WriteFileIfChanged(outFile, outBytes, 0666); err != nil {
  248. ctx.Errorf(err.Error())
  249. }
  250. lateOutBytes := s.writeLate(phonies, dists)
  251. if err := pathtools.WriteFileIfChanged(lateOutFile, lateOutBytes, 0666); err != nil {
  252. ctx.Errorf(err.Error())
  253. }
  254. installsBytes := s.writeInstalls(katiInstalls, katiSymlinks)
  255. if err := pathtools.WriteFileIfChanged(installsFile, installsBytes, 0666); err != nil {
  256. ctx.Errorf(err.Error())
  257. }
  258. // Only save state for tests when testing.
  259. if ctx.Config().RunningInsideUnitTest() {
  260. s.varsForTesting = vars
  261. s.installsForTesting = installsBytes
  262. }
  263. }
  264. func (s *makeVarsSingleton) writeVars(vars []makeVarsVariable) []byte {
  265. buf := &bytes.Buffer{}
  266. fmt.Fprint(buf, `# Autogenerated file
  267. # Compares SOONG_$(1) against $(1), and warns if they are not equal.
  268. #
  269. # If the original variable is empty, then just set it to the SOONG_ version.
  270. #
  271. # $(1): Name of the variable to check
  272. # $(2): If not-empty, sort the values before comparing
  273. # $(3): Extra snippet to run if it does not match
  274. define soong-compare-var
  275. ifneq ($$($(1)),)
  276. my_val_make := $$(strip $(if $(2),$$(sort $$($(1))),$$($(1))))
  277. my_val_soong := $(if $(2),$$(sort $$(SOONG_$(1))),$$(SOONG_$(1)))
  278. ifneq ($$(my_val_make),$$(my_val_soong))
  279. $$(warning $(1) does not match between Make and Soong:)
  280. $(if $(2),$$(warning Make adds: $$(filter-out $$(my_val_soong),$$(my_val_make))),$$(warning Make : $$(my_val_make)))
  281. $(if $(2),$$(warning Soong adds: $$(filter-out $$(my_val_make),$$(my_val_soong))),$$(warning Soong: $$(my_val_soong)))
  282. $(3)
  283. endif
  284. my_val_make :=
  285. my_val_soong :=
  286. else
  287. $(1) := $$(SOONG_$(1))
  288. endif
  289. .KATI_READONLY := $(1) SOONG_$(1)
  290. endef
  291. my_check_failed := false
  292. `)
  293. // Write all the strict checks out first so that if one of them errors,
  294. // we get all of the strict errors printed, but not the non-strict
  295. // warnings.
  296. for _, v := range vars {
  297. if !v.strict {
  298. continue
  299. }
  300. sort := ""
  301. if v.sort {
  302. sort = "true"
  303. }
  304. fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value)
  305. fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s,my_check_failed := true))\n\n", v.name, sort)
  306. }
  307. fmt.Fprint(buf, `
  308. ifneq ($(my_check_failed),false)
  309. $(error Soong variable check failed)
  310. endif
  311. my_check_failed :=
  312. `)
  313. for _, v := range vars {
  314. if v.strict {
  315. continue
  316. }
  317. sort := ""
  318. if v.sort {
  319. sort = "true"
  320. }
  321. fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value)
  322. fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s))\n\n", v.name, sort)
  323. }
  324. fmt.Fprintln(buf, "\nsoong-compare-var :=")
  325. fmt.Fprintln(buf)
  326. return buf.Bytes()
  327. }
  328. func (s *makeVarsSingleton) writeLate(phonies []phony, dists []dist) []byte {
  329. buf := &bytes.Buffer{}
  330. fmt.Fprint(buf, `# Autogenerated file
  331. # Values written by Soong read after parsing all Android.mk files.
  332. `)
  333. for _, phony := range phonies {
  334. fmt.Fprintf(buf, ".PHONY: %s\n", phony.name)
  335. fmt.Fprintf(buf, "%s: %s\n", phony.name, strings.Join(phony.deps, "\\\n "))
  336. }
  337. fmt.Fprintln(buf)
  338. for _, dist := range dists {
  339. fmt.Fprintf(buf, ".PHONY: %s\n", strings.Join(dist.goals, " "))
  340. fmt.Fprintf(buf, "$(call dist-for-goals,%s,%s)\n",
  341. strings.Join(dist.goals, " "), strings.Join(dist.paths, " "))
  342. }
  343. return buf.Bytes()
  344. }
  345. // writeInstalls writes the list of install rules generated by Soong to a makefile. The rules
  346. // are exported to Make instead of written directly to the ninja file so that main.mk can add
  347. // the dependencies from the `required` property that are hard to resolve in Soong.
  348. func (s *makeVarsSingleton) writeInstalls(installs, symlinks []katiInstall) []byte {
  349. buf := &bytes.Buffer{}
  350. fmt.Fprint(buf, `# Autogenerated file
  351. # Values written by Soong to generate install rules that can be amended by Kati.
  352. `)
  353. preserveSymlinksFlag := "-d"
  354. if runtime.GOOS == "darwin" {
  355. preserveSymlinksFlag = "-R"
  356. }
  357. for _, install := range installs {
  358. // Write a rule for each install request in the form:
  359. // to: from [ deps ] [ | order only deps ]
  360. // cp -f -d $< $@ [ && chmod +x $@ ]
  361. fmt.Fprintf(buf, "%s: %s", install.to.String(), install.from.String())
  362. for _, dep := range install.implicitDeps {
  363. fmt.Fprintf(buf, " %s", dep.String())
  364. }
  365. if extraFiles := install.extraFiles; extraFiles != nil {
  366. fmt.Fprintf(buf, " %s", extraFiles.zip.String())
  367. }
  368. if len(install.orderOnlyDeps) > 0 {
  369. fmt.Fprintf(buf, " |")
  370. }
  371. for _, dep := range install.orderOnlyDeps {
  372. fmt.Fprintf(buf, " %s", dep.String())
  373. }
  374. fmt.Fprintln(buf)
  375. fmt.Fprintln(buf, "\t@echo \"Install: $@\"")
  376. fmt.Fprintf(buf, "\trm -f $@ && cp -f %s $< $@\n", preserveSymlinksFlag)
  377. if install.executable {
  378. fmt.Fprintf(buf, "\tchmod +x $@\n")
  379. }
  380. if extraFiles := install.extraFiles; extraFiles != nil {
  381. fmt.Fprintf(buf, "\t( unzip -qDD -d '%s' '%s' 2>&1 | grep -v \"zipfile is empty\"; exit $${PIPESTATUS[0]} ) || \\\n", extraFiles.dir.String(), extraFiles.zip.String())
  382. fmt.Fprintf(buf, "\t ( code=$$?; if [ $$code -ne 0 -a $$code -ne 1 ]; then exit $$code; fi )\n")
  383. }
  384. fmt.Fprintln(buf)
  385. }
  386. for _, symlink := range symlinks {
  387. fmt.Fprintf(buf, "%s:", symlink.to.String())
  388. if symlink.from != nil {
  389. // The symlink doesn't need updating when the target is modified, but we sometimes
  390. // have a dependency on a symlink to a binary instead of to the binary directly, and
  391. // the mtime of the symlink must be updated when the binary is modified, so use a
  392. // normal dependency here instead of an order-only dependency.
  393. fmt.Fprintf(buf, " %s", symlink.from.String())
  394. }
  395. for _, dep := range symlink.implicitDeps {
  396. fmt.Fprintf(buf, " %s", dep.String())
  397. }
  398. if len(symlink.orderOnlyDeps) > 0 {
  399. fmt.Fprintf(buf, " |")
  400. }
  401. for _, dep := range symlink.orderOnlyDeps {
  402. fmt.Fprintf(buf, " %s", dep.String())
  403. }
  404. fmt.Fprintln(buf)
  405. fromStr := ""
  406. if symlink.from != nil {
  407. rel, err := filepath.Rel(filepath.Dir(symlink.to.String()), symlink.from.String())
  408. if err != nil {
  409. panic(fmt.Errorf("failed to find relative path for symlink from %q to %q: %w",
  410. symlink.from.String(), symlink.to.String(), err))
  411. }
  412. fromStr = rel
  413. } else {
  414. fromStr = symlink.absFrom
  415. }
  416. fmt.Fprintln(buf, "\t@echo \"Symlink: $@\"")
  417. fmt.Fprintf(buf, "\trm -f $@ && ln -sfn %s $@", fromStr)
  418. fmt.Fprintln(buf)
  419. fmt.Fprintln(buf)
  420. }
  421. return buf.Bytes()
  422. }
  423. func (c *makeVarsContext) DeviceConfig() DeviceConfig {
  424. return DeviceConfig{c.Config().deviceConfig}
  425. }
  426. var ninjaDescaper = strings.NewReplacer("$$", "$")
  427. func (c *makeVarsContext) Eval(ninjaStr string) (string, error) {
  428. s, err := c.SingletonContext.Eval(c.pctx, ninjaStr)
  429. if err != nil {
  430. return "", err
  431. }
  432. // SingletonContext.Eval returns an exapnded string that is valid for a ninja file, de-escape $$ to $ for use
  433. // in a Makefile
  434. return ninjaDescaper.Replace(s), nil
  435. }
  436. func (c *makeVarsContext) addVariableRaw(name, value string, strict, sort bool) {
  437. c.vars = append(c.vars, makeVarsVariable{
  438. name: name,
  439. value: value,
  440. strict: strict,
  441. sort: sort,
  442. })
  443. }
  444. func (c *makeVarsContext) addVariable(name, ninjaStr string, strict, sort bool) {
  445. value, err := c.Eval(ninjaStr)
  446. if err != nil {
  447. c.SingletonContext.Errorf(err.Error())
  448. }
  449. c.addVariableRaw(name, value, strict, sort)
  450. }
  451. func (c *makeVarsContext) addPhony(name string, deps []string) {
  452. c.phonies = append(c.phonies, phony{name, deps})
  453. }
  454. func (c *makeVarsContext) addDist(goals []string, paths []string) {
  455. c.dists = append(c.dists, dist{
  456. goals: goals,
  457. paths: paths,
  458. })
  459. }
  460. func (c *makeVarsContext) Strict(name, ninjaStr string) {
  461. c.addVariable(name, ninjaStr, true, false)
  462. }
  463. func (c *makeVarsContext) StrictSorted(name, ninjaStr string) {
  464. c.addVariable(name, ninjaStr, true, true)
  465. }
  466. func (c *makeVarsContext) StrictRaw(name, value string) {
  467. c.addVariableRaw(name, value, true, false)
  468. }
  469. func (c *makeVarsContext) Check(name, ninjaStr string) {
  470. c.addVariable(name, ninjaStr, false, false)
  471. }
  472. func (c *makeVarsContext) CheckSorted(name, ninjaStr string) {
  473. c.addVariable(name, ninjaStr, false, true)
  474. }
  475. func (c *makeVarsContext) CheckRaw(name, value string) {
  476. c.addVariableRaw(name, value, false, false)
  477. }
  478. func (c *makeVarsContext) Phony(name string, deps ...Path) {
  479. c.addPhony(name, Paths(deps).Strings())
  480. }
  481. func (c *makeVarsContext) DistForGoal(goal string, paths ...Path) {
  482. c.DistForGoals([]string{goal}, paths...)
  483. }
  484. func (c *makeVarsContext) DistForGoalWithFilename(goal string, path Path, filename string) {
  485. c.DistForGoalsWithFilename([]string{goal}, path, filename)
  486. }
  487. func (c *makeVarsContext) DistForGoals(goals []string, paths ...Path) {
  488. c.addDist(goals, Paths(paths).Strings())
  489. }
  490. func (c *makeVarsContext) DistForGoalsWithFilename(goals []string, path Path, filename string) {
  491. c.addDist(goals, []string{path.String() + ":" + filename})
  492. }