bazel.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. // Copyright 2021 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. "bufio"
  17. "errors"
  18. "strings"
  19. "github.com/google/blueprint"
  20. "github.com/google/blueprint/proptools"
  21. "android/soong/android/allowlists"
  22. )
  23. const (
  24. // A sentinel value to be used as a key in Bp2BuildConfig for modules with
  25. // no package path. This is also the module dir for top level Android.bp
  26. // modules.
  27. Bp2BuildTopLevel = "."
  28. )
  29. // FileGroupAsLibrary describes a filegroup module that is converted to some library
  30. // such as aidl_library or proto_library.
  31. type FileGroupAsLibrary interface {
  32. ShouldConvertToAidlLibrary(ctx BazelConversionPathContext) bool
  33. ShouldConvertToProtoLibrary(ctx BazelConversionPathContext) bool
  34. GetAidlLibraryLabel(ctx BazelConversionPathContext) string
  35. GetProtoLibraryLabel(ctx BazelConversionPathContext) string
  36. }
  37. type BazelConversionStatus struct {
  38. // Information about _all_ bp2build targets generated by this module. Multiple targets are
  39. // supported as Soong handles some things within a single target that we may choose to split into
  40. // multiple targets, e.g. renderscript, protos, yacc within a cc module.
  41. Bp2buildInfo []bp2buildInfo `blueprint:"mutated"`
  42. // UnconvertedBp2buildDep stores the module names of direct dependency that were not converted to
  43. // Bazel
  44. UnconvertedDeps []string `blueprint:"mutated"`
  45. // MissingBp2buildDep stores the module names of direct dependency that were not found
  46. MissingDeps []string `blueprint:"mutated"`
  47. }
  48. type bazelModuleProperties struct {
  49. // The label of the Bazel target replacing this Soong module. When run in conversion mode, this
  50. // will import the handcrafted build target into the autogenerated file. Note: this may result in
  51. // a conflict due to duplicate targets if bp2build_available is also set.
  52. Label *string
  53. // If true, bp2build will generate the converted Bazel target for this module. Note: this may
  54. // cause a conflict due to the duplicate targets if label is also set.
  55. //
  56. // This is a bool pointer to support tristates: true, false, not set.
  57. //
  58. // To opt in a module, set bazel_module: { bp2build_available: true }
  59. // To opt out a module, set bazel_module: { bp2build_available: false }
  60. // To defer the default setting for the directory, do not set the value.
  61. Bp2build_available *bool
  62. // CanConvertToBazel is set via InitBazelModule to indicate that a module type can be converted to
  63. // Bazel with Bp2build.
  64. CanConvertToBazel bool `blueprint:"mutated"`
  65. }
  66. // Properties contains common module properties for Bazel migration purposes.
  67. type properties struct {
  68. // In "Bazel mixed build" mode, this represents the Bazel target replacing
  69. // this Soong module.
  70. Bazel_module bazelModuleProperties
  71. }
  72. // namespacedVariableProperties is a map from a string representing a Soong
  73. // config variable namespace, like "android" or "vendor_name" to a slice of
  74. // pointer to a struct containing a single field called Soong_config_variables
  75. // whose value mirrors the structure in the Blueprint file.
  76. type namespacedVariableProperties map[string][]interface{}
  77. // BazelModuleBase contains the property structs with metadata for modules which can be converted to
  78. // Bazel.
  79. type BazelModuleBase struct {
  80. bazelProperties properties
  81. // namespacedVariableProperties is used for soong_config_module_type support
  82. // in bp2build. Soong config modules allow users to set module properties
  83. // based on custom product variables defined in Android.bp files. These
  84. // variables are namespaced to prevent clobbering, especially when set from
  85. // Makefiles.
  86. namespacedVariableProperties namespacedVariableProperties
  87. // baseModuleType is set when this module was created from a module type
  88. // defined by a soong_config_module_type. Every soong_config_module_type
  89. // "wraps" another module type, e.g. a soong_config_module_type can wrap a
  90. // cc_defaults to a custom_cc_defaults, or cc_binary to a custom_cc_binary.
  91. // This baseModuleType is set to the wrapped module type.
  92. baseModuleType string
  93. }
  94. // Bazelable is specifies the interface for modules that can be converted to Bazel.
  95. type Bazelable interface {
  96. bazelProps() *properties
  97. HasHandcraftedLabel() bool
  98. HandcraftedLabel() string
  99. GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string
  100. ShouldConvertWithBp2build(ctx BazelConversionContext) bool
  101. shouldConvertWithBp2build(ctx bazelOtherModuleContext, module blueprint.Module) bool
  102. ConvertWithBp2build(ctx TopDownMutatorContext)
  103. // namespacedVariableProps is a map from a soong config variable namespace
  104. // (e.g. acme, android) to a map of interfaces{}, which are really
  105. // reflect.Struct pointers, representing the value of the
  106. // soong_config_variables property of a module. The struct pointer is the
  107. // one with the single member called Soong_config_variables, which itself is
  108. // a struct containing fields for each supported feature in that namespace.
  109. //
  110. // The reason for using a slice of interface{} is to support defaults
  111. // propagation of the struct pointers.
  112. namespacedVariableProps() namespacedVariableProperties
  113. setNamespacedVariableProps(props namespacedVariableProperties)
  114. BaseModuleType() string
  115. SetBaseModuleType(baseModuleType string)
  116. }
  117. // ApiProvider is implemented by modules that contribute to an API surface
  118. type ApiProvider interface {
  119. ConvertWithApiBp2build(ctx TopDownMutatorContext)
  120. }
  121. // MixedBuildBuildable is an interface that module types should implement in order
  122. // to be "handled by Bazel" in a mixed build.
  123. type MixedBuildBuildable interface {
  124. // IsMixedBuildSupported returns true if and only if this module should be
  125. // "handled by Bazel" in a mixed build.
  126. // This "escape hatch" allows modules with corner-case scenarios to opt out
  127. // of being built with Bazel.
  128. IsMixedBuildSupported(ctx BaseModuleContext) bool
  129. // QueueBazelCall invokes request-queueing functions on the BazelContext
  130. // so that these requests are handled when Bazel's cquery is invoked.
  131. QueueBazelCall(ctx BaseModuleContext)
  132. // ProcessBazelQueryResponse uses Bazel information (obtained from the BazelContext)
  133. // to set module fields and providers to propagate this module's metadata upstream.
  134. // This effectively "bridges the gap" between Bazel and Soong in a mixed build.
  135. // Soong modules depending on this module should be oblivious to the fact that
  136. // this module was handled by Bazel.
  137. ProcessBazelQueryResponse(ctx ModuleContext)
  138. }
  139. // BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
  140. type BazelModule interface {
  141. Module
  142. Bazelable
  143. }
  144. // InitBazelModule is a wrapper function that decorates a BazelModule with Bazel-conversion
  145. // properties.
  146. func InitBazelModule(module BazelModule) {
  147. module.AddProperties(module.bazelProps())
  148. module.bazelProps().Bazel_module.CanConvertToBazel = true
  149. }
  150. // bazelProps returns the Bazel properties for the given BazelModuleBase.
  151. func (b *BazelModuleBase) bazelProps() *properties {
  152. return &b.bazelProperties
  153. }
  154. func (b *BazelModuleBase) namespacedVariableProps() namespacedVariableProperties {
  155. return b.namespacedVariableProperties
  156. }
  157. func (b *BazelModuleBase) setNamespacedVariableProps(props namespacedVariableProperties) {
  158. b.namespacedVariableProperties = props
  159. }
  160. func (b *BazelModuleBase) BaseModuleType() string {
  161. return b.baseModuleType
  162. }
  163. func (b *BazelModuleBase) SetBaseModuleType(baseModuleType string) {
  164. b.baseModuleType = baseModuleType
  165. }
  166. // HasHandcraftedLabel returns whether this module has a handcrafted Bazel label.
  167. func (b *BazelModuleBase) HasHandcraftedLabel() bool {
  168. return b.bazelProperties.Bazel_module.Label != nil
  169. }
  170. // HandcraftedLabel returns the handcrafted label for this module, or empty string if there is none
  171. func (b *BazelModuleBase) HandcraftedLabel() string {
  172. return proptools.String(b.bazelProperties.Bazel_module.Label)
  173. }
  174. // GetBazelLabel returns the Bazel label for the given BazelModuleBase.
  175. func (b *BazelModuleBase) GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
  176. if b.HasHandcraftedLabel() {
  177. return b.HandcraftedLabel()
  178. }
  179. if b.ShouldConvertWithBp2build(ctx) {
  180. return bp2buildModuleLabel(ctx, module)
  181. }
  182. return "" // no label for unconverted module
  183. }
  184. type Bp2BuildConversionAllowlist struct {
  185. // Configure modules in these directories to enable bp2build_available: true or false by default.
  186. defaultConfig allowlists.Bp2BuildConfig
  187. // Keep any existing BUILD files (and do not generate new BUILD files) for these directories
  188. // in the synthetic Bazel workspace.
  189. keepExistingBuildFile map[string]bool
  190. // Per-module allowlist to always opt modules into both bp2build and Bazel Dev Mode mixed
  191. // builds. These modules are usually in directories with many other modules that are not ready
  192. // for conversion.
  193. //
  194. // A module can either be in this list or its directory allowlisted entirely
  195. // in bp2buildDefaultConfig, but not both at the same time.
  196. moduleAlwaysConvert map[string]bool
  197. // Per-module-type allowlist to always opt modules in to both bp2build and
  198. // Bazel Dev Mode mixed builds when they have the same type as one listed.
  199. moduleTypeAlwaysConvert map[string]bool
  200. // Per-module denylist to always opt modules out of bp2build conversion.
  201. moduleDoNotConvert map[string]bool
  202. }
  203. // NewBp2BuildAllowlist creates a new, empty Bp2BuildConversionAllowlist
  204. // which can be populated using builder pattern Set* methods
  205. func NewBp2BuildAllowlist() Bp2BuildConversionAllowlist {
  206. return Bp2BuildConversionAllowlist{
  207. allowlists.Bp2BuildConfig{},
  208. map[string]bool{},
  209. map[string]bool{},
  210. map[string]bool{},
  211. map[string]bool{},
  212. }
  213. }
  214. // SetDefaultConfig copies the entries from defaultConfig into the allowlist
  215. func (a Bp2BuildConversionAllowlist) SetDefaultConfig(defaultConfig allowlists.Bp2BuildConfig) Bp2BuildConversionAllowlist {
  216. if a.defaultConfig == nil {
  217. a.defaultConfig = allowlists.Bp2BuildConfig{}
  218. }
  219. for k, v := range defaultConfig {
  220. a.defaultConfig[k] = v
  221. }
  222. return a
  223. }
  224. // SetKeepExistingBuildFile copies the entries from keepExistingBuildFile into the allowlist
  225. func (a Bp2BuildConversionAllowlist) SetKeepExistingBuildFile(keepExistingBuildFile map[string]bool) Bp2BuildConversionAllowlist {
  226. if a.keepExistingBuildFile == nil {
  227. a.keepExistingBuildFile = map[string]bool{}
  228. }
  229. for k, v := range keepExistingBuildFile {
  230. a.keepExistingBuildFile[k] = v
  231. }
  232. return a
  233. }
  234. // SetModuleAlwaysConvertList copies the entries from moduleAlwaysConvert into the allowlist
  235. func (a Bp2BuildConversionAllowlist) SetModuleAlwaysConvertList(moduleAlwaysConvert []string) Bp2BuildConversionAllowlist {
  236. if a.moduleAlwaysConvert == nil {
  237. a.moduleAlwaysConvert = map[string]bool{}
  238. }
  239. for _, m := range moduleAlwaysConvert {
  240. a.moduleAlwaysConvert[m] = true
  241. }
  242. return a
  243. }
  244. // SetModuleTypeAlwaysConvertList copies the entries from moduleTypeAlwaysConvert into the allowlist
  245. func (a Bp2BuildConversionAllowlist) SetModuleTypeAlwaysConvertList(moduleTypeAlwaysConvert []string) Bp2BuildConversionAllowlist {
  246. if a.moduleTypeAlwaysConvert == nil {
  247. a.moduleTypeAlwaysConvert = map[string]bool{}
  248. }
  249. for _, m := range moduleTypeAlwaysConvert {
  250. a.moduleTypeAlwaysConvert[m] = true
  251. }
  252. return a
  253. }
  254. // SetModuleDoNotConvertList copies the entries from moduleDoNotConvert into the allowlist
  255. func (a Bp2BuildConversionAllowlist) SetModuleDoNotConvertList(moduleDoNotConvert []string) Bp2BuildConversionAllowlist {
  256. if a.moduleDoNotConvert == nil {
  257. a.moduleDoNotConvert = map[string]bool{}
  258. }
  259. for _, m := range moduleDoNotConvert {
  260. a.moduleDoNotConvert[m] = true
  261. }
  262. return a
  263. }
  264. // ShouldKeepExistingBuildFileForDir returns whether an existing BUILD file should be
  265. // added to the build symlink forest based on the current global configuration.
  266. func (a Bp2BuildConversionAllowlist) ShouldKeepExistingBuildFileForDir(dir string) bool {
  267. if _, ok := a.keepExistingBuildFile[dir]; ok {
  268. // Exact dir match
  269. return true
  270. }
  271. var i int
  272. // Check if subtree match
  273. for {
  274. j := strings.Index(dir[i:], "/")
  275. if j == -1 {
  276. return false //default
  277. }
  278. prefix := dir[0 : i+j]
  279. i = i + j + 1 // skip the "/"
  280. if recursive, ok := a.keepExistingBuildFile[prefix]; ok && recursive {
  281. return true
  282. }
  283. }
  284. }
  285. var bp2BuildAllowListKey = NewOnceKey("Bp2BuildAllowlist")
  286. var bp2buildAllowlist OncePer
  287. func GetBp2BuildAllowList() Bp2BuildConversionAllowlist {
  288. return bp2buildAllowlist.Once(bp2BuildAllowListKey, func() interface{} {
  289. return NewBp2BuildAllowlist().SetDefaultConfig(allowlists.Bp2buildDefaultConfig).
  290. SetKeepExistingBuildFile(allowlists.Bp2buildKeepExistingBuildFile).
  291. SetModuleAlwaysConvertList(allowlists.Bp2buildModuleAlwaysConvertList).
  292. SetModuleTypeAlwaysConvertList(allowlists.Bp2buildModuleTypeAlwaysConvertList).
  293. SetModuleDoNotConvertList(allowlists.Bp2buildModuleDoNotConvertList)
  294. }).(Bp2BuildConversionAllowlist)
  295. }
  296. // MixedBuildsEnabled returns true if a module is ready to be replaced by a
  297. // converted or handcrafted Bazel target. As a side effect, calling this
  298. // method will also log whether this module is mixed build enabled for
  299. // metrics reporting.
  300. func MixedBuildsEnabled(ctx BaseModuleContext) bool {
  301. module := ctx.Module()
  302. mixedBuildEnabled := ctx.Config().IsMixedBuildsEnabled() &&
  303. ctx.Os() != Windows && // Windows toolchains are not currently supported.
  304. module.Enabled() &&
  305. convertedToBazel(ctx, module) &&
  306. ctx.Config().BazelContext.IsModuleNameAllowed(module.Name())
  307. ctx.Config().LogMixedBuild(ctx, mixedBuildEnabled)
  308. return mixedBuildEnabled
  309. }
  310. // ConvertedToBazel returns whether this module has been converted (with bp2build or manually) to Bazel.
  311. func convertedToBazel(ctx BazelConversionContext, module blueprint.Module) bool {
  312. b, ok := module.(Bazelable)
  313. if !ok {
  314. return false
  315. }
  316. return b.shouldConvertWithBp2build(ctx, module) || b.HasHandcraftedLabel()
  317. }
  318. // ShouldConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build
  319. func (b *BazelModuleBase) ShouldConvertWithBp2build(ctx BazelConversionContext) bool {
  320. return b.shouldConvertWithBp2build(ctx, ctx.Module())
  321. }
  322. type bazelOtherModuleContext interface {
  323. ModuleErrorf(format string, args ...interface{})
  324. Config() Config
  325. OtherModuleType(m blueprint.Module) string
  326. OtherModuleName(m blueprint.Module) string
  327. OtherModuleDir(m blueprint.Module) string
  328. }
  329. func (b *BazelModuleBase) shouldConvertWithBp2build(ctx bazelOtherModuleContext, module blueprint.Module) bool {
  330. if !b.bazelProps().Bazel_module.CanConvertToBazel {
  331. return false
  332. }
  333. // In api_bp2build mode, all soong modules that can provide API contributions should be converted
  334. // This is irrespective of its presence/absence in bp2build allowlists
  335. if ctx.Config().BuildMode == ApiBp2build {
  336. _, providesApis := module.(ApiProvider)
  337. return providesApis
  338. }
  339. propValue := b.bazelProperties.Bazel_module.Bp2build_available
  340. packagePath := ctx.OtherModuleDir(module)
  341. // Modules in unit tests which are enabled in the allowlist by type or name
  342. // trigger this conditional because unit tests run under the "." package path
  343. isTestModule := packagePath == Bp2BuildTopLevel && proptools.BoolDefault(propValue, false)
  344. if isTestModule {
  345. return true
  346. }
  347. moduleName := module.Name()
  348. allowlist := ctx.Config().Bp2buildPackageConfig
  349. moduleNameAllowed := allowlist.moduleAlwaysConvert[moduleName]
  350. moduleTypeAllowed := allowlist.moduleTypeAlwaysConvert[ctx.OtherModuleType(module)]
  351. allowlistConvert := moduleNameAllowed || moduleTypeAllowed
  352. if moduleNameAllowed && moduleTypeAllowed {
  353. ctx.ModuleErrorf("A module cannot be in moduleAlwaysConvert and also be in moduleTypeAlwaysConvert")
  354. return false
  355. }
  356. if allowlist.moduleDoNotConvert[moduleName] {
  357. if moduleNameAllowed {
  358. ctx.ModuleErrorf("a module cannot be in moduleDoNotConvert and also be in moduleAlwaysConvert")
  359. }
  360. return false
  361. }
  362. // This is a tristate value: true, false, or unset.
  363. if ok, directoryPath := bp2buildDefaultTrueRecursively(packagePath, allowlist.defaultConfig); ok {
  364. if moduleNameAllowed {
  365. ctx.ModuleErrorf("A module cannot be in a directory marked Bp2BuildDefaultTrue"+
  366. " or Bp2BuildDefaultTrueRecursively and also be in moduleAlwaysConvert. Directory: '%s'"+
  367. " Module: '%s'", directoryPath, moduleName)
  368. return false
  369. }
  370. // Allow modules to explicitly opt-out.
  371. return proptools.BoolDefault(propValue, true)
  372. }
  373. // Allow modules to explicitly opt-in.
  374. return proptools.BoolDefault(propValue, allowlistConvert)
  375. }
  376. // bp2buildDefaultTrueRecursively checks that the package contains a prefix from the
  377. // set of package prefixes where all modules must be converted. That is, if the
  378. // package is x/y/z, and the list contains either x, x/y, or x/y/z, this function will
  379. // return true.
  380. //
  381. // However, if the package is x/y, and it matches a Bp2BuildDefaultFalse "x/y" entry
  382. // exactly, this module will return false early.
  383. //
  384. // This function will also return false if the package doesn't match anything in
  385. // the config.
  386. //
  387. // This function will also return the allowlist entry which caused a particular
  388. // package to be enabled. Since packages can be enabled via a recursive declaration,
  389. // the path returned will not always be the same as the one provided.
  390. func bp2buildDefaultTrueRecursively(packagePath string, config allowlists.Bp2BuildConfig) (bool, string) {
  391. // Check if the package path has an exact match in the config.
  392. if config[packagePath] == allowlists.Bp2BuildDefaultTrue || config[packagePath] == allowlists.Bp2BuildDefaultTrueRecursively {
  393. return true, packagePath
  394. } else if config[packagePath] == allowlists.Bp2BuildDefaultFalse || config[packagePath] == allowlists.Bp2BuildDefaultFalseRecursively {
  395. return false, packagePath
  396. }
  397. // If not, check for the config recursively.
  398. packagePrefix := packagePath
  399. // e.g. for x/y/z, iterate over x/y, then x, taking the most-specific value from the allowlist.
  400. for strings.Contains(packagePrefix, "/") {
  401. dirIndex := strings.LastIndex(packagePrefix, "/")
  402. packagePrefix = packagePrefix[:dirIndex]
  403. switch value := config[packagePrefix]; value {
  404. case allowlists.Bp2BuildDefaultTrueRecursively:
  405. // package contains this prefix and this prefix should convert all modules
  406. return true, packagePrefix
  407. case allowlists.Bp2BuildDefaultFalseRecursively:
  408. //package contains this prefix and this prefix should NOT convert any modules
  409. return false, packagePrefix
  410. }
  411. // Continue to the next part of the package dir.
  412. }
  413. return false, packagePath
  414. }
  415. func registerBp2buildConversionMutator(ctx RegisterMutatorsContext) {
  416. ctx.TopDown("bp2build_conversion", convertWithBp2build).Parallel()
  417. }
  418. func convertWithBp2build(ctx TopDownMutatorContext) {
  419. bModule, ok := ctx.Module().(Bazelable)
  420. if !ok || !bModule.shouldConvertWithBp2build(ctx, ctx.Module()) {
  421. return
  422. }
  423. bModule.ConvertWithBp2build(ctx)
  424. }
  425. func registerApiBp2buildConversionMutator(ctx RegisterMutatorsContext) {
  426. ctx.TopDown("apiBp2build_conversion", convertWithApiBp2build).Parallel()
  427. }
  428. // Generate API contribution targets if the Soong module provides APIs
  429. func convertWithApiBp2build(ctx TopDownMutatorContext) {
  430. if m, ok := ctx.Module().(ApiProvider); ok {
  431. m.ConvertWithApiBp2build(ctx)
  432. }
  433. }
  434. // GetMainClassInManifest scans the manifest file specified in filepath and returns
  435. // the value of attribute Main-Class in the manifest file if it exists, or returns error.
  436. // WARNING: this is for bp2build converters of java_* modules only.
  437. func GetMainClassInManifest(c Config, filepath string) (string, error) {
  438. file, err := c.fs.Open(filepath)
  439. if err != nil {
  440. return "", err
  441. }
  442. defer file.Close()
  443. scanner := bufio.NewScanner(file)
  444. for scanner.Scan() {
  445. line := scanner.Text()
  446. if strings.HasPrefix(line, "Main-Class:") {
  447. return strings.TrimSpace(line[len("Main-Class:"):]), nil
  448. }
  449. }
  450. return "", errors.New("Main-Class is not found.")
  451. }
  452. func AttachValidationActions(ctx ModuleContext, outputFilePath Path, validations Paths) ModuleOutPath {
  453. validatedOutputFilePath := PathForModuleOut(ctx, "validated", outputFilePath.Base())
  454. ctx.Build(pctx, BuildParams{
  455. Rule: CpNoPreserveSymlink,
  456. Description: "run validations " + outputFilePath.Base(),
  457. Output: validatedOutputFilePath,
  458. Input: outputFilePath,
  459. Validations: validations,
  460. })
  461. return validatedOutputFilePath
  462. }