Gyp是一个类似CMake的项目生成工具, 用于管理你的源代码, 在google code主页上唯一的一句slogan是”GYP can Generate Your Projects.”。GYP是由 Chromium 团队开发的跨平台自动化项目构建工具,Chromium便是通过GYP进行项目构建管理。
function getNodeDir () {
// 'python' should be set by now
process.env.PYTHON = python
if (gyp.opts.nodedir) {
// --nodedir was specified. use that for the dev files
nodeDir = gyp.opts.nodedir.replace(/^~/, osenv.home())
log.verbose('get node dir', 'compiling against specified --nodedir dev files: %s', nodeDir)
createBuildDir()
} else {
gyp.commands.install([ release.version ], function (err, version) {
if (err) return callback(err)
log.verbose('get node dir', 'target node version installed:', release.versionDir)
nodeDir = path.resolve(gyp.devDir, release.versionDir)
createBuildDir()
})
}
}
找到node所在目录,如果没有,则下载node压缩包并解压。
3.
function createBuildDir () {
log.verbose('build dir', 'attempting to create "build" dir: %s', buildDir)
mkdirp(buildDir, function (err, isNew) {
if (err) return callback(err)
log.verbose('build dir', '"build" dir needed to be created?', isNew)
if (win && (!gyp.opts.msvs_version || gyp.opts.msvs_version === '2017')) {
findVS2017(function (err, vsSetup) {
if (err) {
log.verbose('Not using VS2017:', err.message)
createConfigFile()
} else {
createConfigFile(null, vsSetup)
}
})
} else {
createConfigFile()
}
})
}
function createConfigFile (err, vsSetup) {
if (err) return callback(err)
var configFilename = 'config.gypi'
var configPath = path.resolve(buildDir, configFilename)
if (vsSetup) {
// GYP doesn't (yet) have support for VS2017, so we force it to VS2015
// to avoid pulling a floating patch that has not landed upstream.
// Ref: https://chromium-review.googlesource.com/#/c/433540/
gyp.opts.msvs_version = '2015'
process.env['GYP_MSVS_VERSION'] = 2015
process.env['GYP_MSVS_OVERRIDE_PATH'] = vsSetup.path
defaults['msbuild_toolset'] = 'v141'
defaults['msvs_windows_target_platform_version'] = vsSetup.sdk
variables['msbuild_path'] = path.join(vsSetup.path, 'MSBuild', '15.0',
'Bin', 'MSBuild.exe')
}
// loop through the rest of the opts and add the unknown ones as variables.
// this allows for module-specific configure flags like:
//
// $ node-gyp configure --shared-libxml2
Object.keys(gyp.opts).forEach(function (opt) {
if (opt === 'argv') return
if (opt in gyp.configDefs) return
variables[opt.replace(/-/g, '_')] = gyp.opts[opt]
})
configs.push(configPath)
fs.writeFile(configPath, [prefix, json, ''].join('\n'), findConfigs)
}
这里创建config.gypi文件,主要包含target_defaults和variables。
5.
// config = ['config.gypi']
function runGyp (err) {
if (err) return callback(err)
if (!~argv.indexOf('-f') && !~argv.indexOf('--format')) {
if (win) {
log.verbose('gyp', 'gyp format was not specified; forcing "msvs"')
// force the 'make' target for non-Windows
argv.push('-f', 'msvs')
} else {
log.verbose('gyp', 'gyp format was not specified; forcing "make"')
// force the 'make' target for non-Windows
argv.push('-f', 'make')
}
}
if (win && !hasMsvsVersion()) {
if ('msvs_version' in gyp.opts) {
argv.push('-G', 'msvs_version=' + gyp.opts.msvs_version)
} else {
argv.push('-G', 'msvs_version=auto')
}
}
// include all the ".gypi" files that were found
configs.forEach(function (config) {
argv.push('-I', config)
})
// For AIX and z/OS we need to set up the path to the exports file
// which contains the symbols needed for linking.
var node_exp_file = undefined
if (process.platform === 'aix' || process.platform === 'os390') {
var ext = process.platform === 'aix' ? 'exp' : 'x'
var node_root_dir = findNodeDirectory()
var candidates = undefined
if (process.platform === 'aix') {
candidates = ['include/node/node',
'out/Release/node',
'out/Debug/node',
'node'
].map(function(file) {
return file + '.' + ext
})
} else {
candidates = ['out/Release/obj.target/libnode',
'out/Debug/obj.target/libnode',
'lib/libnode'
].map(function(file) {
return file + '.' + ext
})
}
var logprefix = 'find exports file'
node_exp_file = findAccessibleSync(logprefix, node_root_dir, candidates)
if (node_exp_file !== undefined) {
log.verbose(logprefix, 'Found exports file: %s', node_exp_file)
} else {
var msg = msgFormat('Could not find node.%s file in %s', ext, node_root_dir)
log.error(logprefix, 'Could not find exports file')
return callback(new Error(msg))
}
}
// this logic ported from the old `gyp_addon` python file
var gyp_script = path.resolve(__dirname, '..', 'gyp', 'gyp_main.py')
var addon_gypi = path.resolve(__dirname, '..', 'addon.gypi')
var common_gypi = path.resolve(nodeDir, 'include/node/common.gypi')
fs.stat(common_gypi, function (err, stat) {
if (err)
common_gypi = path.resolve(nodeDir, 'common.gypi')
var output_dir = 'build'
if (win) {
// Windows expects an absolute path
output_dir = buildDir
}
var nodeGypDir = path.resolve(__dirname, '..')
var nodeLibFile = path.join(nodeDir,
!gyp.opts.nodedir ? '<(target_arch)' : '$(Configuration)',
release.name + '.lib')
argv.push('-I', addon_gypi)
argv.push('-I', common_gypi)
argv.push('-Dlibrary=shared_library')
argv.push('-Dvisibility=default')
argv.push('-Dnode_root_dir=' + nodeDir)
if (process.platform === 'aix' || process.platform === 'os390') {
argv.push('-Dnode_exp_file=' + node_exp_file)
}
argv.push('-Dnode_gyp_dir=' + nodeGypDir)
argv.push('-Dnode_lib_file=' + nodeLibFile)
argv.push('-Dmodule_root_dir=' + process.cwd())
argv.push('-Dnode_engine=' +
(gyp.opts.node_engine || process.jsEngine || 'v8'))
argv.push('--depth=.')
argv.push('--no-parallel')
// tell gyp to write the Makefile/Solution files into output_dir
argv.push('--generator-output', output_dir)
// tell make to write its output into the same dir
argv.push('-Goutput_dir=.')
// enforce use of the "binding.gyp" file
argv.unshift('binding.gyp')
// execute `gyp` from the current target nodedir
argv.unshift(gyp_script)
// make sure python uses files that came with this particular node package
var pypath = [path.join(__dirname, '..', 'gyp', 'pylib')]
if (process.env.PYTHONPATH) {
pypath.push(process.env.PYTHONPATH)
}
process.env.PYTHONPATH = pypath.join(win ? ';' : ':')
var cp = gyp.spawn(python, argv)
cp.on('exit', onCpExit)
})
}
在我们写node addon时,需要使用node-gyp命令行工具,大部分同学会用
configue
生成配置文件,然后使用build
进行构建。但是node-gyp到底是什么?底层有什么呢?下面我们来刨根问底。本文的线索是自底向上的讲解node-gyp的各层次依赖,主要有以下几个部分:
层次结构如下图所示:
make
从源文件到可执行文件叫做编译(包括预编译、编译、链接),而make作为构建工具掌握着编译的过程,也就是如何去编译、文件编译的顺序等。
make是最常用的构建工具,针对用户制定的构建规则(makefile)去执行响应的任务。make会根据构建规则去查找依赖,决定编译顺序等。大致了解可参考Make 命令教程
Makefile(makefile)中定义了make的构建规则,当然也可以自己指定规则文件。例如:
Makefile由一条条的规则组成,每条规则由target(目标)、source(前置条件/依赖)、command(指令)三者组成。
形式如下:
当
make target
时,主要做了以下几件事:以编译一个C++文件的规则为例:
当我们执行make hellomake,会使用gcc编译器编译产出hellomake。如果make不带有参数,则执行makefile中的第一条指令。
make也允许我们定义一些纯指令(伪指令)去执行一些操作,相当于把上面的target写成指令名称,只不过在command中不生成文件,所以每次执行该规则时都会执行command。为了和真实的目标文件做区分,make中使用了
.PHONY
关键字,关键字.PHONY可以解决这问题,告诉make该目标是“假的”(磁盘上其实没有这个目标文件)。例如由于makefile目标只能写一个,所以我们可以使用all来将多个目标组合起来。例如:
一般情况下可以把all放在makefile的第一行,这样不带参数执行make就会找到all。
make install
make install用来安装文件,它从Makefile中读取指令,安装到系统目录中。
cmake
上面提到了make,似乎已经够了,如果我是一个开发者,我定义了makefile,让使用者执行make编译就好了。但是不同平台的编译器、动态链接库的路径都有可能不同,如果想让你的软件能够跨平台编译、运行,必须要保证能够在不同平台编译。如果使用上面的Make工具,就得为每一种标准写一次Makefile,这是很繁琐并且容易出错的地方。
cmake的出现就是为了解决上述问题,它首先允许开发者编写一种平台无关的 CMakeList.txt文件来定制整个编译流程,cmake会根据操作系统选择不同编译器,当然也可以在CMakeList.txt中去指定,执行cmake时会目标用户的平台和自定义的配置生成所需的Makefile或工程文件,如Unix的Makefile、Windows的Visual Studio。
CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。
在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:
CMakeList.txt中由面向过程的一条条指令组成,例如:
具体可参考cmake文档
GYP
Gyp是一个类似CMake的项目生成工具, 用于管理你的源代码, 在google code主页上唯一的一句slogan是”GYP can Generate Your Projects.”。GYP是由 Chromium 团队开发的跨平台自动化项目构建工具,Chromium便是通过GYP进行项目构建管理。
首先看GYP与cmake类似,那为什要有GYP呢?GYP和cmake有哪些相同点、不同点呢?
GYP vs cmake
相同点:
支持跨平台项目工程文件输出,Windows 平台默认是 Visual Studio,Linux 平台默认是 Makefile,Mac 平台默认是 Xcode,这个功能 CMake 也同样支持,只是缺少了 Xcode。
不同点:
配置文件形式不同,GYP的配置文件更像一个“配置文件”,而Cmake的上述所言更像一个面向过程的一个脚本,也就是说在项目设置的层次上进行抽象;同时GYP支持交叉编译。
具体比较可参考GYP vs. CMake
GYP配置
GYP的配置文件以
.gyp
结尾,一个典型的.gyp
文件如下所示:variables
: 定义可以在文件其他地方访问的变量;includes
: 将要被引入到该文件中的文件列表,通常是以.gypi结尾的文件
;target_defaults
: 将作用域所有目标的默认配置;targets
: 构建的目标列表,每个target中包含构建此目标的所有配置;conditions
: 条件列表,会根据不同条件选择不同的配置项。在最顶级的配置中,通常是平台特定的目标配置。具体可参考GYP文档
node-gyp
node-gyp是一个跨平台的命令行工具,目的是编译node addon模块。
常用的命令有
configure
和build
,configure
原理就是利用gyp生成不同的编译配置文件,build
则根据不同平台、不同构建配置进行编译。configure
我们分步骤看下configure的代码:
1.
由于GYP是python写的,所以这里首先找当前系统下的python,内部利用的是
which
这个第三方库。2.
找到node所在目录,如果没有,则下载node压缩包并解压。
3.
创建build目录,这里区分了是否有vs,查找vs的方法是打开powershell(windows),试图打开vs。
4.
这里创建
config.gypi文件
,主要包含target_defaults
和variables
。5.
这里主要是区分了不同平台,给GYP命令加入各种参数,其中
-I
代表include,最后执行gyp脚本生成构建配置文件,比如unix下生成makefile。build
build
比较简单,言简意赅就是就是区分不同平台,收集不同参数,利用不同编译工具进行编译。1.
区分编译工具。
2.
加载
config.gypi
,为构建收集一波参数。如果在windows下,收集build/*.sln
。3.
执行编译命令。