[design decision]让工具依赖于naming convention是个拙劣的做法
先大概描述一下环境:
这是一个C++的build system, 基于premake扩展而来,可以管理各个package[1]/library之间的dependency关系,每个package都会随binary发布一个lua文件用来描述其导出的library和依赖的package,比如:
LIBRARIES = {}
DEPENDENCIES = {}从而把整个dependency closure给串起来。
现在的需求要用户可以在这个lua文件中定义自己的函数,让用户在定义他们的project的时候调用,目的是为consumer在使用library时提供更精确的控制。比如在boost的use.lua中定义:
function use_boost_thread()
links('boost_1_46_1_thread')
-- do anything special...
end并且foo依赖于boost:
project('foo')
kind('SharedLib')
use_boost_thread()
应该说这是一个很灵活的设计, 给了库的发布者与使用者更大的控制力,但premake该如何来实现呢?
用户定义在他们的use.lua的文件中的函数,并不是直接可用的 - 因为在premake运行过程中有很多use.lua文件会被load,而他们需要被load到一个独立的table中(通过setfenv)以避免LIBRARIES/DEPENDENCIES的名字冲突,所以我们需要把这些被load到独立table中的函数引入到全局表中[2],比如:
_G['functionname'] = ns.use_boost_thread()
问题是,用户可以在他们的use.lua中定义很多函数,你需要知道哪个应该被导入到全局表中 - 用户必须通过某种方法来告诉我,什么方法? 一个方法是通过naming convention,比如凡是要导出的函数都用use_开头,然后只要把所有use_开头的函数导入即可,但这种方法的问题的不灵活,不健壮,而且至显的有点愚蠢:
- 如果我要到处某个函数,他就必须以这种丑陋的use_开头
- 如果某个内部函数无意中起了个use_开头的名字,就被不幸的导出了
- 修改函数是否导出,竟然需要修改函数名
那么有更好的方法吗? 有,通过某个数据结构明确指定,比如:
EXPORTS = {'use_boost_thread'} function use_boost_thread() links('boost_1_46_1_thread') -- do anything special... end
然后,你可以任意命名你的函数,然后把需要导出的放到EXPORTS表中。
处理的时候,先读入EXPORTS,找到导出的函数名,并把他们导出。
这相当于加了了间接层,这不由得让我想起了一句话:All problems in computer science can be solved by another level of indirection[3]。
当然,有另外一个更好的方法是给函数加属性(C#/Java),或者加修饰(C++中的__declspec(dllimport)/visibility('hidden')啥的, 能力比较有限),更加简洁明了。
[1] package的概念是一系列需要共同发布的library,比如boost,或者opencv;用户使用一个package,但并不一定要link所有该package中library, 可能只是link其中之一。
[2] 一个参考实现:https://gist.github.com/4133666
[3] http://en.wikipedia.org/wiki/Indirection