├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── emake.py
├── history.txt
└── sample
├── build.bat
├── build.sh
├── testmain.c
├── testsrc1.c
└── testsrc1.h
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.pyo
3 |
4 | __pycache__/
5 |
6 | /.vscode/*
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | cache: pip
3 | python:
4 | - 2.7
5 | - 3.6
6 | matrix:
7 | allow_failures:
8 | - python: 3.6
9 | install:
10 | #- pip install -r requirements.txt
11 | - pip install flake8 # pytest # add another testing frameworks later
12 | before_script:
13 | # stop the build if there are Python syntax errors or undefined names
14 | - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
15 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
16 | - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
17 | script:
18 | - true # pytest --capture=sys # add other tests here
19 | notifications:
20 | on_success: change
21 | on_failure: change # `always` will be the setting once code changes slow down
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Preface
2 |
3 | GNU Make 太麻烦?Makefile 写起来太臃肿?头文件依赖生成搞不定?多核同时编译不好弄?Emake 帮你解决这些问题:
4 |
5 | - 使用简单:设定源文件,设定编译参数和输出目标就行了,emake为你打点好一切。
6 | - 依赖分析:快速分析源代码所依赖的头文件,决定是否需要重新编译。
7 | - 输出模式:可执行、静态库(.a)、动态库(.so/.dll)。
8 | - 多核编译:轻松实现并行编译,加速项目构建。
9 | - 精简紧凑:只有唯一的一个 emake.py 文件。
10 | - 干净利索:无需导出 Makefile/.sln 等中间文件,构建一步到位。
11 | - 交叉编译:构建 iOS 项目 ,安卓项目,等等。
12 | - 语言支持 `C` / `C++` / `ObjC` / `ObjC++` / `ASM`
13 | - 工具支持 `gcc` / `mingw` / `clang`
14 | - 运行系统 `Windows` / `Linux` / `Mac OS X` / `FreeBSD`
15 | - 信息导出:项目文件列表,目标文件,以及 `compile_commands.json` 等。
16 | - 包管理支持 `pkg-config`,`vcpkg` 和手工等三种方式导入第三方包。
17 | - 方便的交叉编译,轻松构建 `Android NDK` / `iOS` / `asm.js` 项目。
18 | - 你见过最简单的构建系统,比 Gnu Make / CMake 都简单很多。
19 |
20 | 只有两三个源代码,那 makefile 随便写,文件一多,搞依赖都可以搞死人。emake 就是简单中的简单,不但比 GNU Make 简单,还要比 cmake 简单很多。
21 |
22 | 应为 CMake 和 GNU Make 都是**命令式构建工具**,而 emake 是**定义式构建工具**,命令式当然可以处理各种复杂情况,本身就是一门编程语言,强大却失之复杂,而定义式类似 IDE 那样,设定文件,编译参数链接参数,就能开始工作了,虽然做不到命令式那么灵活,但能满足大多数中小型项目开发,个人实验项目的日常开发。
23 |
24 | Emake 是为快速开发而生的,通过牺牲了部分灵活性,却换来了极大的便利性,最初版本在 2009年发布,多年间团队在不同操作系统下用它构建过:服务端项目、客户端项目、iOS项目、安卓项目 和 Flash项目,这些项目都稳健的跑在生产环境中,为海量用户提供服务。
25 |
26 | 多年的开发中,emake 提高了各种大小项目的开发效率,自身也随着时间增加不断被完善和稳定。
27 |
28 | 具体好用在哪里,实际使用比 cmake/xmake 简单在哪里?可以看这个介绍:
29 |
30 | [Emake:你见过最简单的 C/C++ 构建工具](https://skywind.me/blog/archives/2768)。
31 |
32 |
33 | ## Content
34 |
35 | - [Preface](#preface)
36 | - [Content](#content)
37 | - [Install](#install)
38 | - [Linux / Mac OS X](#linux--mac-os-x)
39 | - [Windows](#windows)
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 | - [TODO](#todo)
65 |
66 |
67 | ## Install
68 |
69 | #### Linux / Mac OS X
70 |
71 | ```bash
72 | wget http://skywind3000.github.io/emake/emake.py
73 | sudo python emake.py -i
74 | ```
75 |
76 | 运行上面两条指令,十秒内完成安装。emake 会拷贝自己到 /usr/local/bin 下面,后面直接使用 emake 指令操作。
77 |
78 | #### Windows
79 |
80 | 下载 emake.py,放到你的 mingw 根目录下(便于 emake 定位 gcc),并且添加到 PATH 环境变量,同级目录新建立一个 emake.cmd 文件,内容如下:
81 |
82 | ```batch
83 | @echo off
84 | d:\dev\python311\python.exe d:\dev\mingw\emake.py %*
85 | ```
86 |
87 | 修改一下对应路径即可,建立这个 emake.cmd 的批处理文件是为了方便每次敲 emake 就可以工作,避免敲 "python emake.py" 一长串。
88 |
89 | ## 快速开始
90 |
91 | 假设你有三个文件:foo.c, bar.c, main.c 共同编译成名字为 main(.exe) 的可执行文件,我们创建 “main.mak” 文件:
92 |
93 | ```make
94 | ; 指明目标格式:exe, lib, dll 三选一
95 | mode: exe
96 |
97 | ; 加入源文件
98 | src: foo.c
99 | src: bar.c
100 | src: main.c
101 | ```
102 |
103 | 是不是比 makefile, cmake 之类的步骤简单多了?编译项目:
104 |
105 | ```bash
106 | emake main.mak
107 | ```
108 |
109 | 好了,工程顺利编译成功,每次任何一个文件发生变动,相关对其依赖的源文件都会重新编译,而无依赖的代码则不需要再次编译。
110 |
111 | #### 增加编译选项
112 |
113 | 如果需要增加编译选项的话:
114 |
115 | ```make
116 | ; 指明目标格式:exe, lib, dll 三选一
117 | mode: exe
118 |
119 | ; 编译选项
120 | flag: -Wall, -O3, -g
121 |
122 | ; 加入源文件
123 | src: foo.c
124 | src: bar.c
125 | src: main.c
126 |
127 | ```
128 |
129 | 如果项目中使用了数学库 libm.a的话:
130 |
131 | ```make
132 | link: m
133 | ```
134 |
135 | 如果还是用了 libstdc++.a 的话:
136 |
137 | ```make
138 | link: m, stdc++
139 | ```
140 |
141 | 或者:
142 |
143 | ```make
144 | link: m
145 | link: stdc++
146 | ```
147 |
148 | link 可以直接写 .a 库的文件名:
149 |
150 | ```make
151 | link: ./lib/libmylib.a
152 | ```
153 |
154 | 如果需要添加额外的 include 目录 和 lib 目录的话:
155 |
156 | ```make
157 | inc: /usr/local/opt/jdk/include
158 | lib: /usr/local/opt/jdk/lib
159 | ```
160 |
161 | 还可以手动指定输出的文件名:
162 |
163 | ```make
164 | out: main
165 | ```
166 |
167 | 手动指定临时文件夹,避免临时 .o 文件污染当前目录的话:
168 |
169 | ```make
170 | int: objs
171 | ```
172 |
173 | 这样所有的临时文件就会跑到 objs 目录下面了,想要清理的话,删除 objs目录即可。
174 |
175 | #### 完整例子
176 |
177 | ```make
178 | ; 指明目标格式:exe, lib, dll 三选一
179 | mode: exe
180 |
181 | ; 编译选项
182 | flag: -Wall, -O3, -g
183 |
184 | ; 设定链接
185 | link: m, pthread, stdc++
186 |
187 | ; 额外头文件路径
188 | inc: /usr/local/opt/jdk/include
189 | inc: /usr/local/opt/jdk/include/linux
190 |
191 | ; 额外库文件路径
192 | lib: /usr/local/opt/jdk/lib
193 |
194 | ; 加入源文件
195 | src: foo.c
196 | src: bar.c
197 | src: main.c
198 | ```
199 |
200 | #### 零工程文件
201 |
202 | 写工程文件还是觉得累?没关系,上面所有工程配置都可以用 **docstring** 的方式写在源文件里,使用 `//!` 开头的注释中,比如 main.cpp 里:
203 |
204 | ```cpp
205 | #include
206 |
207 | //! mode: exe
208 | //! flag: -Wall, -O3, -g
209 | //! link: m, pthread
210 | //! src: foo.c, bar.c
211 | int main(void) {
212 | ...
213 | return 0;
214 | }
215 | ```
216 |
217 | 源文件中 `//!` 开头的特殊注释会被 emake 提取,作为工程配置的内容。这个例子基本揽括了上面独立工程文件做的事情,当然 `src:` 部分 main.cpp 是会被自动加入的,不用额外写,然后构建时:
218 |
219 | ```bash
220 | emake main.cpp
221 | ```
222 |
223 | 即可,简单到了极点,特别适合验证一些小想法,做一些小实验,不用写一大堆乱七八糟的东西。
224 |
225 | #### 绝对路径
226 |
227 | 为了避免有时编译错误输出时,文件名用的相对路径,造成外部编辑器无法正确解析,可以用 `--abs` 参数:
228 |
229 | ```bash
230 | emake --abs main.cpp
231 | ```
232 |
233 | 这样错误输出中的文件名,就是绝对路径了。
234 |
235 | #### 使用技巧
236 |
237 | - [如何 vcpkg 集成到 emake 中?](https://github.com/skywind3000/emake/wiki/%E5%A6%82%E4%BD%95-vcpkg-%E9%9B%86%E6%88%90%E5%88%B0-emake-%E4%B8%AD%EF%BC%9F)
238 | - [如何进行交叉编译?](https://github.com/skywind3000/emake/wiki/%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91%EF%BC%9F)
239 | - [如何在 Windows 项目中添加 RC 文件?](https://github.com/skywind3000/emake/wiki/%E5%A6%82%E4%BD%95%E5%9C%A8-Windows-%E9%A1%B9%E7%9B%AE%E4%B8%AD%E6%B7%BB%E5%8A%A0-RC-%E6%96%87%E4%BB%B6%EF%BC%9F)
240 |
241 | 更多技巧见 “[FAQ:常见问题集](https://github.com/skywind3000/emake/wiki/Frequently-Asked-Questions)” 的 wiki 页面。
242 |
243 |
244 | ## 工程配置说明
245 |
246 | Emake 的工程文件里面支持下面几种核心设置:
247 |
248 | | 名称 | 含义 |
249 | |-|-|
250 | | src | 指定项目源文件,逗号分割,支持通配符 |
251 | | inc | 指定 include 目录,逗号分割,支持相对路径(相对于工程文件)|
252 | | lib | 指定库文件目录,格式同上 |
253 | | link | 指定库文件,逗号分割,比如 `lib: png` 就会连接 `libpng.a` |
254 | | flag | 指定编译通用选项,逗号分割 |
255 | | cflag | 指定 C 语言的编译选项,逗号分割 |
256 | | cxxflag | 指定 C++ 的编译选项,逗号分割 |
257 | | define | 定义宏,格式为 `define: USE_UTF8=1`,可用逗号间隔多个宏 |
258 | | mode | 设置目标格式:`exe`,`lib`, `dll`, `win` 几种 |
259 | | out | 设置目标文件名 |
260 | | int | 设置临时目录,放置 `.o` 文件和中间文件 |
261 | | flnk | 连接时传入的参数,逗号分割 |
262 | | wlnk | 连接时使用 `-Wl,` 前缀直接透传给 `ld` 的参数,逗号分割 |
263 |
264 | 还支持几种辅助配置:
265 |
266 | | 名称 | 含义 |
267 | |-|-|
268 | | export | 当 mode 为 dll 时导出符号成 `.lib` 文件给 MSVC 用 |
269 | | import | 从 emake.ini 配置的非 `default` 区导入配置 |
270 | | package | 从 pkg-config 导入某个包配置,并设置 inc/lib 目录和 link 选项 |
271 | | pcflag | 调用 pkg-config 时的参数 |
272 | | echo | 输出文字 |
273 | | color | 设置 echo 的颜色 |
274 | | preload | 事件:加载前,后接 shell 命令 |
275 | | prebuild | 事件:编译前,后接 shell 命令 |
276 | | prelink | 事件:连接前,后接 shell 命令 |
277 | | postbuild | 事件:构建后,即连接成功了就调用,后接 shell 命令 |
278 | | environ | 设置环境变量 |
279 |
280 | 下面对其中几项略作说明。
281 |
282 | #### 添加源代码
283 |
284 | 用于声明项目里面的源文件,格式:
285 |
286 | ```make
287 | src: file1
288 | src: file2
289 | ...
290 | src: filen
291 | ```
292 |
293 | 或者:
294 |
295 | ```make
296 | src: file1, file2, file3
297 | src: file4, file5, file6
298 | ```
299 |
300 | 也可以带通配符:
301 |
302 | ```make
303 | src: core/*.c
304 | src: source/*.cpp
305 | ```
306 |
307 | #### 目录设置
308 |
309 | 声明项目中的 include 文件夹,相当于 gcc 的 `-I` 命令:
310 |
311 | ```make
312 | inc: dir1
313 | inc: dir2
314 | ```
315 |
316 | 或者:
317 |
318 | ```make
319 | inc: dir1, dir2
320 | ```
321 |
322 | 和 src 一样可以使用逗号分隔;而 `lib` 选项用于设置库文件目录,相当于 gcc 的 `-L` 命令,格式和 `inc` 类似。
323 |
324 | #### 连接静态库
325 |
326 | 添加需要链接的库,相当于 gcc 的 -l 指令:
327 |
328 | ```make
329 | link: m, pthread, stdc++
330 | ```
331 |
332 | 或者:
333 |
334 | ```make
335 | link: m
336 | link: pthread
337 | link: stdc++
338 | ```
339 |
340 | 同时支持单行和多行模式,编译 C++ 项目别忘记链接 stdc++。
341 |
342 | #### 目标格式
343 |
344 | 目标文件的输出格式:
345 |
346 | ```make
347 | mode: [exe|lib|dll|win]
348 | ```
349 |
350 | - exe: 生成可执行文件
351 | - lib: 生成静态链接库
352 | - dll: 生成动态链接库
353 | - win: Windows 下特有,生成无 console 窗口的 Windows 程序。
354 |
355 | #### 临时目录
356 |
357 | 指定中间临时文件目录,一般设置为:
358 |
359 | ```make
360 | int: objs
361 | ```
362 |
363 | 或者:
364 |
365 | ```make
366 | int: objs/$(target)
367 | ```
368 |
369 | 指定了以后,可以避免项目中间文件污染当前目录。
370 |
371 | #### 条件编译
372 |
373 | 在 emake.ini 中可以指明一系列 name,比如:
374 |
375 | ```ini
376 | [default]
377 | name=android,posix,arm,nossl
378 | ```
379 |
380 | 每个名字代表一个条件,可以同时定义多个条件,然后在工程文件里使用:
381 |
382 | ```text
383 | /option: value
384 | ```
385 |
386 | 来声明,只有 emake.ini 的 name 里包含特定内容时,才会触发后面内容,比如:
387 |
388 | ```make
389 | win32/link: pdcurses_wincon
390 | linux/link: ncurses, tinfo
391 | arm/src: arm_calculate.c
392 | ```
393 |
394 | 这两条语句代表不同平台 link 不同的库,`name` 可以不定义,它的默认值是 target,你可以只定义一个 target 不定义 name:
395 |
396 | ```ini
397 | [default]
398 | target=android
399 | ```
400 |
401 | 这样手工指明目标平台的名称,默认的话,会使用 python 里的 `sys.platform` 返回值。同时你又没有定义过 `name` 项目,那么 `name` 的默认值就是 `target`,除非你和上面一样手工定义 `name`,就能覆盖默认值。
402 |
403 | 所以 `name` 只有一个时,一般不定义,用默认值即可,但有多个 `name` 时需要定义下。
404 |
405 | #### 编译配置
406 |
407 | 在启动 emake 时可以设置一个 `--profile` 参数来指定 debug/release 等不同的构建配置:
408 |
409 | ```bash
410 | python emake.py --profile=debug main.mak
411 | ```
412 |
413 | 而在工程文件里,用 `@` 符号来指定某个配置是属于什么 profile 的,比如:
414 |
415 | ```make
416 | flag@debug: -g, -Og
417 | flag@release: -O2
418 | flag@minsize: -Os
419 | flnk@minsize: -s
420 | ```
421 |
422 | 那么 emake 就会根据命令行传入的 profile 选择对应的配置项目,再比如,给不同配置规定不同的目标文件名:
423 |
424 | ```make
425 | win32/out: rogue-clone.exe
426 | win32/out@debug: rogue-cloned.exe
427 | linux/out: rogue-clone
428 | linux/out@debug: rogue-cloned
429 | ```
430 |
431 | 这里分别演示了在不同平台下,不同 profile 可以指定不同的配置。
432 |
433 | #### 细粒度参数
434 |
435 | Emake 支持为每个源代码文件设置不同的编译参数,格式是就是在 `src` 定义完源文件后,右边加冒号跟着参数即可:
436 |
437 | ```make
438 | src: main.c : -O3
439 | src: foo.c, bar.c : -DTEST
440 | ```
441 |
442 | 上面第一行单独为 `main.c` 设置了 `-O3` 的参数,第二行为另外两个源文件定义了一个叫做 `TEST` 的宏。
443 |
444 | #### 事件机制
445 |
446 | 根据条件运行不同 shell 命令:
447 |
448 | ```
449 | # 加载前
450 | preload: echo "preload"
451 |
452 | # 编译前
453 | prebuild: echo "prebuild"
454 |
455 | # 链接前
456 | prelink: echo "prelink"
457 |
458 | # 编译后
459 | postbuild: echo "postbuild"
460 | ```
461 |
462 | 其中 `preload` 和 `prebuild` 之间的区别是,`preload` 每次都会运行,并且是在解析项目文件和分析依赖之前运行,而 `prebuild` 是在解析项目文件分析依赖之后运行,如果二进制没更新,`prebuild` 就不会被调用,而 `preload` 每次都无条件被执行。
463 |
464 | 比如某个源代码是使用其他工具生成的,放到 `prebuild` 时,应为处于依赖分析之后,初次构建尚未触发生成代码前面依赖分析就会报找不到文件了,但 `preload` 在依赖分析之前运行。
465 |
466 | 在事件运行时,可以从环境变量中读取一些信息:
467 |
468 | - `$EMAKE_MAIN`: 工程文件路径。
469 | - `$EMAKE_OUT`: 输出文件路径。
470 | - `$EMAKE_MODE`: 模式,可执行还是动态库。
471 | - `$EMAKE_SCRIPT`: 脚本(emake.py)的路径。
472 | - `$EMAKE_HOME`: 工程文件所在目录,即 `$EMAKE_MAIN` 的 dirname。
473 | - `$EMAKE_INT`: 中间文件的目录。
474 | - `$EMAKE_TOOLCHAIN`: 工具链的目录。
475 | - `$EMAKE_TARGET`: 目标平台。
476 |
477 | 比如 Windows 下面:
478 |
479 | ```make
480 | postbuild: echo %EMAKE_OUT%
481 | ```
482 |
483 | 就能每次编译后显示输出文件的路径了。
484 |
485 | 最后这些事件命令会在工程文件所在的目录被启动。
486 |
487 |
488 | ## 工具链配置
489 |
490 | Emake 支持多个工具链,每个工具链使用一个 ini 进行描述,不显示指定工具链的名字的话,会到下面几个位置寻找默认工具链配置:
491 |
492 | 1)在 `emake.py` 同级目录查找 `emake.ini` 。
493 | 2)/etc/emake.ini
494 | 3)/usr/local/etc/emake.ini
495 | 4)~/.config/emake.ini
496 |
497 | 默认工具链配置并不强制,没提供的话,Emake 也会到 `$PATH` 中寻找 gcc 等工具并自动设置。
498 |
499 | 如果想用别的工具链配置的话,使用 `--cfg=name` 参数就会加载 `~/.config/emake/{name}.ini` :
500 |
501 | emake --cfg=mingw64
502 |
503 | 这样它会去试图加载 `~/.config/emake/mingw64.ini`,也可以用 `--ini=path` 直接给 ini 文件绝对路径:
504 |
505 | emake --ini=/absolute/path/to/name.ini
506 |
507 | 通常使用 `--cfg=name`,并在 `~/.config/emake` 目录下统一管理所有配置,比如你有多套工具链,每个工具链一个配置放进去,用起来比较方便,特别是有交叉编译的情况时。
508 |
509 | #### 配置格式
510 |
511 | 工具链配置文件的内容类似:
512 |
513 | ```ini
514 | [default]
515 | # 工具链的 bin 目录,用于查找 gcc / clang 等工具
516 | home=d:/msys32/mingw32/bin
517 | # 当你有多套工具链时,不可能都加入 $PATH,这个配置可以让 emake 在
518 | # 构建时临时追加到 $PATH 前面,不污染外层父进程的环境变量
519 | path=d:/msys32/mingw32/bin,d:/msys32/usr/bin
520 |
521 | # 设置传参模式,如果使用 clang,则取消下面注释
522 | # option=x
523 |
524 | # 通用配置,免得每个工程文件写一遍
525 | flag=-Wall
526 | link=stdc++, winmm, wsock32, user32, ws2_32
527 | cflag=-std=c11
528 | cxxflag=-std=c++17
529 |
530 | # 针对 debug/release/static 三种 profile 的设置,使用
531 | # emake --profile= xxx 在构建时指明使用啥 profile
532 | define@debug=_DEBUG=1
533 | define@release=_RELEASE=1
534 | define@static=_STATIC=1, _RELEASE=1
535 |
536 | flag@debug=-Og, -g, -fno-omit-frame-pointer
537 | flag@release=-O3
538 | flag@static=-O3, -static
539 |
540 | # 多核编译
541 | cpu=4
542 |
543 | # 目标平台名称,不提供得话默认用 python 的 sys.platform 字符串代替
544 | target=win32
545 |
546 | # 条件编译时候的条件变量,在工程文件里可以用 win32/flag: xxx 来使用
547 | name=win32,nt,have_openssl
548 | ```
549 |
550 | 配置都包括在 ini 的 `[default]` 区,其中第一行使用 `home` 定义了工具链的 `bin` 目录,在哪里将找到 `gcc`, `ar`, `as`, `ld` 等工具,可以用绝对路径,也可以用相对于 ini 文件的路径。
551 |
552 | 接下来使用 `path` 定义了调用工具链前需要把哪些目录追加到系统变量 `$PATH` 前,使用逗号分割。
553 |
554 | 中间就是一些通用参数,和针对 `debug`, `release` 和 `static` 三种 profile 的不同设置,然后用 `cpu=4` 指定了同时使用四核编译。
555 |
556 | 最后用 `target` 声明目标平台的名字是 `win32`。
557 |
558 | 这样的 ini 配置文件除了上面默认的位置加载外,还可以手工指定。
559 |
560 |
561 | #### 配置项目
562 |
563 | 工具链配置文件的 `[default]` 区支持下面几种配置:
564 |
565 | | 名称 | 含义 |
566 | |-|-|
567 | | home | 工具链的 bin 目录,下面可以找到 gcc 和 ld 等工具,可以用绝对路径或相对于 ini 文件的路径,不提供的话会搜索 `$PATH` 中的 gcc,比如默认工具链配置可以不写,如果 gcc 刚好在你的 `$PATH` 中的话 |
568 | | gcc | gcc 工具的可执行文件名,默认不提供的话,就会试图去调用 "gcc",如果不是这个名字的话,可以用该选项配置一下,比如设置成 "clang" 可以调用 clang,而很多交叉编译工具链前面一般有一串前缀,比如 "arm-linux-androideabi-gcc" 这也,也需要设置 |
569 | | ar | 静态库工具名,同上 |
570 | | ld | 连接器工具名,同上 |
571 | | as | 汇编器工具名,同上 |
572 | | dllwrap | 动态库封装工具名,同上 |
573 | | pkgconfig | pkgconfig 工具名,默认为 `pkg-config` |
574 | | path | 调用工具时额外需要加入 `$PATH` 的路径,用 `,` 分割多个项目;很多工具链的 bin 目录并不会加入系统的 `$PATH`,但里面的工具又会互相调用,使用这个选项可以不污染外部环境变量,只有在 emake 进程里临时设置 `$PATH` 变量,干净一些 |
575 | | option | 扩展配置字符串,如果使用 clang,需要设置 `option=x` 模式,因为 lld 和 ld 的传参有一些不一样的地方,不能使用 `-Xlinker` 模式 |
576 | | cpu | 后面跟一个数字,多核编译的核心数量 |
577 | | target | 目标平台名称,比如 `win32` 和 `linux`,不提供的话会使用 Python 的 `sys.platform` 作为默认值 |
578 | | name | 条件编译的条件变量名称,逗号分割,比如 "android,posix,nossl",注意 target 的值会自动加入到 name 中,方便根据目标平台进行条件判断 |
579 | | environ | 逗号分隔的环境变量,比如 `environ=FOO=BAR,BARZ=1`,调用工具链前初始化 |
580 | | pcpath | 设置环境变量 `$PKG_CONFIG_PATH` |
581 | | pcflag | 设置 `pkg-config` 的公用参数 |
582 |
583 | 上面的配置用于确定工具链的位置和运行方式,下面这些用于通用项目配置
584 |
585 | | 名称 | 含义 |
586 | |-|-|
587 | | include | 额外的全局 include 目录,逗号分割,类似项目配置里的 `inc` 选项,如果有一些多个项目都需要设置的 include 目录,可以把它设置到工具链配置里,这也就不用每个项目都写了 |
588 | | lib | 额外的全局 lib 目录,逗号分割,类似项目配置里的 `lib` 选项 |
589 | | flag | 全局编译参数,类似项目配置里的 `flag` 选项,编译具体项目时,会添加到项目配置的 flag 前面 |
590 | | cflag| 全局 C 语言编译参数,类似项目配置里的 `cflag` 选项 |
591 | | cxxflag | 全局 C++ 编译参数,类似项目配置里的 `cxxflag` 选项 |
592 | | link | 全局挂载库,比如一些所有项目都要挂载的库 `pthread`, `stdc++` 之类的,写在这里,就不用每个项目都写 |
593 | | define | 全局宏定义 |
594 | | flnk | 连接时传输的参数 |
595 | | wlnk | 连接时使用 `-Wl,` 前缀直接透传给 `ld` 工具的参数 |
596 |
597 |
598 | #### 配置导入
599 |
600 | 在工具链配置的 ini 文件里,在非 `[default]` 区可以写一些额外配置:
601 |
602 | ```ini
603 | [default]
604 | ...
605 |
606 | [ffmpeg]
607 | include=d:/dev/local/opt/ffmpeg/include
608 | lib=d:/dev/local/opt/ffmpeg/lib
609 | link=avcodec, avdevice, avfilter, avformat, avutil, postproc, swscale
610 |
611 | [qt]
612 | include=D:/Dev/Qt/sdk/4.8.3-mingw/include;D:/Dev/Qt/sdk/4.8.3-mingw/include/QtGui
613 | lib=D:/Dev/Qt/sdk/4.8.3-mingw/lib
614 | link=stdc++, ole32, gdi32, wsock32, opengl32, gdi32, glu32, ws2_32, uuid, oleaut32, winmm, imm32, winspool, QtCore4, QtGui4, QtGuid4
615 |
616 | ```
617 |
618 | 那么在项目配置里就可以使用 import 来导入:
619 |
620 | import: qt, ffmpeg
621 |
622 | 那么在你的工程里,上面 qt 和 ffmpeg 的相关配置就会被导入了。
623 |
624 | #### 系统包管理
625 |
626 | 要引入一个包的话,除了用上面的配置导入外,还有一种方式是用 pkg-config,只需要在工程文件里用 `package` 语句即可:
627 |
628 | ```make
629 | package: python3
630 | ```
631 |
632 | 那么 Emake 会在构建前调用 `pkg-config` 查询 `python3` 这个包的各种编译信息,包括 CFLAGS, LDFLAGS 等,并追加到工程配置中。
633 |
634 | 当然 pkg-config 依赖 `/usr/lib` 或者 `/usr/local/bin` 下面的 `.pc` 文件,比如:
635 |
636 | ```text
637 | ./lib/x86_64-linux-gnu/pkgconfig/libxcrypt.pc
638 | ./lib/x86_64-linux-gnu/pkgconfig/lua.pc
639 | ./lib/x86_64-linux-gnu/pkgconfig/formw.pc
640 | ./lib/x86_64-linux-gnu/pkgconfig/lua54.pc
641 | ./lib/x86_64-linux-gnu/pkgconfig/lua5.4-c++.pc
642 | ./lib/x86_64-linux-gnu/pkgconfig/python3.pc
643 | ./lib/x86_64-linux-gnu/pkgconfig/ncurses++.pc
644 | ./lib/x86_64-linux-gnu/pkgconfig/lua-5.4.pc
645 | ```
646 |
647 | 这些 `.pc` 文件是在安装系统包或者编译第三方包时 `make install` 或者 `cmake --install` 命令生成的,主要用于指定各个包的 `CFLAGS` / `LDFLAGS` 等编译参数以及依赖关系。
648 |
649 | 而 `pkg-config` 工具,能够加载这些文件处理好依赖并给出正确的 `CFLAGS` / `LDFLAGS` 编译参数。此外,在工程文件里,还可以给出 `pkg-config` 工具调用的额外参数:
650 |
651 | ```make
652 | pcflag: --atlatest-version
653 | ```
654 |
655 | 这也这个参数就会传递给 `pkg-config`。
656 |
657 | 还可以在工具链配置文件中,指定 `$PKG_CONFIG_PATH` 这个环境变量:
658 |
659 | ```ini
660 | [default]
661 | ...
662 | pcpath=D:/arm32-devkit/lib/pkgconfig
663 | pcflag=--env-only
664 | ...
665 | ```
666 |
667 | 这样调用 `pkg-config` 前会先设置这个环境变量,控制 `pkg-config` 搜索 `.pc` 文件的位置,当然可以配合下一条 `pcflag=--env-only` 跳过默认搜索路径。
668 |
669 | 通常情况下不用特别设置 `pcpath`,因为工具链自带的 `pkg-config` 一般能够根据自身位置正确搜索到工具链内的 `.pc` 文件的,除非你还有其他位置自己管理额外的 `.pc` 或者工具链不提供 `pkg-config` 工具,你需要依赖一个外部的 `pkg-config` 程序。
670 |
671 | 这是推荐的包导入方式,而如果一个包没有 `.pc` 文件,`pkg-config` 找不到的话,可以用上面的 `import` 方式导入一个工具链 ini 配置文件里的 section。
672 |
673 | ## 启动参数
674 |
675 | ```text
676 | usage: "emake.py [options] srcfile" (emake 3.7.3 Aug.27 2024 win32)
677 |
678 | actions : -b | -build build project
679 | -c | -compile compile project
680 | -l | -link link project
681 | -r | -rebuild rebuild project
682 | -e | -execute execute project
683 | -o | -out show output file name
684 | -d | -cmdline call cmdline tool in given environ
685 | -g | -cygwin cygwin execute
686 | -s | -cshell cygwin shell
687 | -i | -install install emake on unix
688 | -u | -update update itself from github
689 | -h | -help show help page
690 |
691 | -home display project home
692 | -list display project files
693 | -objs display obj files
694 | -cflags display compile flags
695 | -depends display dependencies
696 | -dirty display dirty files
697 | -commands display compile commands json
698 |
699 | options : --cfg={cfg} load config from ~/.config/emake/{cfg}.ini
700 | --ini={inipath} load config from {inipath} directly
701 | --profile={name} set profile to {name}
702 | --print={n} set verbose level: 0-3
703 | --abs={0|1} display absolute path in error messages
704 |
705 | Emake is a easy tool which controls the generation of executables and other
706 | non-source files of a program from the program's source files.
707 | ```
708 |
709 | ## 快速开发
710 |
711 | 不管时 GNU Make 还是 cmake,亦或时其他构建系统,都需要你写一个专门的工程文件来描述该工程。对于大项目很正常,但是对于中小项目,特别时一些测试类项目,这真的太麻烦了。
712 |
713 | Emake 可以不用工程文件,而将工程配置信息嵌入到源代码的注释中:
714 |
715 | ```cpp
716 | #include
717 | #include
718 | #include "foobar.h"
719 |
720 |
721 | //! mode: exe
722 | //! src: foo.cpp, bar.cpp, utils.cpp
723 | int main(void)
724 | {
725 | printf("Hello, World !!\n");
726 | foo();
727 | bar();
728 | return 0;
729 | }
730 |
731 | ```
732 |
733 | 这样在你的源文件里面增添两行以后,即可使用:
734 |
735 | emake main.cpp
736 |
737 | 来进行编译,emake 会自动提取 `//!` 开头的注释,解析为 emake的项目描述信息,上面的配置描述了该项目依赖的文件(除了 main.cpp自己外),以及项目模式为生成可执行文件。
738 |
739 | 这样写起来,比所有构建系统都简单很多。
740 |
741 | ## 输出信息
742 |
743 | 列出项目最终输出文件,比如 `xxx.so` 之类:
744 |
745 | ```bash
746 | emake -o hello.mak
747 | ```
748 |
749 | 列出项目所包含的源文件:
750 |
751 | ```bash
752 | emake -list hello.mak
753 | ```
754 |
755 | 列出编译参数:
756 |
757 | ```bash
758 | emake -cflags hello.mak
759 | ```
760 |
761 | 列出依赖:
762 |
763 | ```bash
764 | emake -depends hello.mak
765 | ```
766 |
767 | 输出 `compile_commands.json` 内容:
768 |
769 | ```bash
770 | emake -commands hello.mak
771 | ```
772 |
773 | 还有更多信息,具体见:`emake -h` 说明。
774 |
775 | ## TODO
776 |
777 | - [ ] 加入 PIP,支持 pip 一键安装。
778 |
779 |
--------------------------------------------------------------------------------
/emake.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # vim: set ts=4 sw=4 tw=0 et :
4 | #======================================================================
5 | #
6 | # emake.py - emake version 3.7.9
7 | #
8 | # history of this file:
9 | # 2009.08.20 skywind create this file
10 | # 2009.11.14 skywind new install() method
11 | # 2009.12.22 skywind implementation execute interface
12 | # 2010.01.18 skywind new project info
13 | # 2010.03.14 skywind fixed source lex bug
14 | # 2010.11.03 skywind new 'import' to import config section
15 | # 2010.11.04 skywind new 'export' to export .def, .lib for windll
16 | # 2010.11.27 skywind fixed link sequence with -Xlink -( -)
17 | # 2012.03.26 skywind multiprocess building system, speed up
18 | # 2012.08.18 skywind new 'flnk' to project
19 | # 2012.09.09 skywind new system condition config, optimized
20 | # 2013.12.19 skywind new $(target) config
21 | # 2014.02.09 skywind new build-event and environ
22 | # 2014.04.15 skywind new 'arglink' and 'argcc' config
23 | # 2015.09.03 skywind new replace in config.parameters()
24 | # 2016.01.14 skywind new compile flags with different source file
25 | # 2016.04.27 skywind exit non-zero when error occurs
26 | # 2016.09.01 skywind new lib composite method
27 | # 2016.09.02 skywind more environ variables rather than $(target)
28 | # 2017.08.16 skywind new: cflag, cxxflag, sflag, mflag, mmflag
29 | # 2017.12.20 skywind new: --abs=1 to tell gcc to print fullpath
30 | # 2022.04.03 changning new: update to Python3
31 | # 2023.09.20 skywind new: --profile=debug/release options
32 | # 2023.10.08 skywind new: try "flag@debug: -g"
33 | # 2023.12.07 skywind new: "PATH" item in 'default' section
34 | # 2024.05.06 skywind new: "pkg: xxx" to import pkg-config packages
35 | #
36 | #======================================================================
37 | from __future__ import unicode_literals, print_function
38 | import sys
39 | import time
40 | import os
41 |
42 | if sys.version_info.major == 2:
43 | import ConfigParser as configparser
44 | import cStringIO as cio
45 | # pylint: disable=undefined-variable
46 | range = xrange # noqa: F821
47 | else:
48 | import configparser
49 | import io as cio
50 | unicode = str
51 |
52 |
53 | #----------------------------------------------------------------------
54 | # version info
55 | #----------------------------------------------------------------------
56 | EMAKE_VERSION = '3.7.9'
57 | EMAKE_DATE = 'Mar.22 2025'
58 |
59 | #----------------------------------------------------------------------
60 | # constant value
61 | #----------------------------------------------------------------------
62 | PRINT_MODE_SHOWFILE = 1
63 | PRINT_MODE_SHOWSTAT = 2
64 | PRINT_MODE_SHOWCMD = 4
65 |
66 | PRINT_MODE_BASIC = PRINT_MODE_SHOWFILE | PRINT_MODE_SHOWSTAT
67 | PRINT_MODE_ALL = PRINT_MODE_BASIC | PRINT_MODE_SHOWCMD
68 |
69 | #----------------------------------------------------------------------
70 | # posix shell tools
71 | #----------------------------------------------------------------------
72 | class posix (object):
73 |
74 | @staticmethod
75 | def load_file_content (filename, mode = 'r'):
76 | if hasattr(filename, 'read'):
77 | try: content = filename.read()
78 | except: pass
79 | return content
80 | try:
81 | fp = open(filename, mode)
82 | content = fp.read()
83 | fp.close()
84 | except:
85 | content = None
86 | return content
87 |
88 | # load file and guess encoding
89 | @staticmethod
90 | def load_file_text (filename, encoding = None):
91 | content = posix.load_file_content(filename, 'rb')
92 | if content is None:
93 | return None
94 | if content[:3] == b'\xef\xbb\xbf':
95 | text = content[3:].decode('utf-8')
96 | elif encoding is not None:
97 | text = content.decode(encoding, 'ignore')
98 | else:
99 | text = None
100 | guess = [sys.getdefaultencoding(), 'utf-8']
101 | if sys.stdout and sys.stdout.encoding:
102 | guess.append(sys.stdout.encoding)
103 | try:
104 | import locale
105 | guess.append(locale.getpreferredencoding())
106 | except:
107 | pass
108 | visit = {}
109 | for name in guess + ['gbk', 'ascii', 'latin1']:
110 | if name in visit:
111 | continue
112 | visit[name] = 1
113 | try:
114 | text = content.decode(name)
115 | break
116 | except:
117 | pass
118 | if text is None:
119 | text = content.decode('utf-8', 'ignore')
120 | return text
121 |
122 | @staticmethod
123 | def load_ini (filename, encoding = None):
124 | text = posix.load_file_text(filename, encoding)
125 | config = {}
126 | sect = 'default'
127 | if text is None:
128 | return None
129 | for line in text.split('\n'):
130 | line = line.strip('\r\n\t ')
131 | if not line:
132 | continue
133 | elif line[:1] in ('#', ';'):
134 | continue
135 | elif line.startswith('['):
136 | if line.endswith(']'):
137 | sect = line[1:-1].strip('\r\n\t ')
138 | if sect not in config:
139 | config[sect] = {}
140 | else:
141 | pos = line.find('=')
142 | if pos >= 0:
143 | key = line[:pos].rstrip('\r\n\t ')
144 | val = line[pos + 1:].lstrip('\r\n\t ')
145 | if sect not in config:
146 | config[sect] = {}
147 | config[sect][key] = val
148 | return config
149 |
150 | @staticmethod
151 | def decode_string (text, encoding = None):
152 | hr = ''
153 | if sys.version_info[0] >= 3:
154 | if isinstance(text, str):
155 | return text
156 | else:
157 | # pylint: disable-next=else-if-used
158 | if isinstance(text, unicode): # noqa
159 | return text
160 | if encoding is not None:
161 | return text.decode(encoding, errors = 'ignore')
162 | guess = [sys.getdefaultencoding(), 'utf-8']
163 | if sys.stdout and sys.stdout.encoding:
164 | guess.append(sys.stdout.encoding)
165 | try:
166 | import locale
167 | guess.append(locale.getpreferredencoding())
168 | except:
169 | pass
170 | guess.reverse()
171 | guess += ['gbk', 'ascii', 'latin1']
172 | visit = {}
173 | for name in guess:
174 | if name in visit:
175 | continue
176 | visit[name] = 1
177 | try:
178 | hr = text.decode(name)
179 | return hr
180 | except:
181 | pass
182 | hr = text.decode('utf-8', errors = 'ignore')
183 | return hr
184 |
185 |
186 | #----------------------------------------------------------------------
187 | # preprocessor: C/C++/Java 预处理器
188 | #----------------------------------------------------------------------
189 | class preprocessor(object):
190 |
191 | # 初始化预编译器
192 | def __init__ (self):
193 | self.reset()
194 |
195 | # 生成正文映射,将所有字符串及注释用 "$"和 "`"代替,排除分析干扰
196 | def preprocess (self, text):
197 | content = text
198 | spaces = (' ', '\n', '\t', '\r')
199 |
200 | srctext = cio.StringIO()
201 | srctext.write(text)
202 | srctext.seek(0)
203 | memo = 0
204 | i = 0
205 | length = len(content)
206 | output = srctext.write
207 | while i < length:
208 | char = content[i]
209 | word = content[i:i + 2]
210 | if memo == 0: # 正文中
211 | if word == '/*':
212 | output('``')
213 | i += 2
214 | memo = 1
215 | continue
216 | if word == '//':
217 | output('``')
218 | i += 2
219 | while (i < len(content)) and (content[i] != '\n'):
220 | if content[i] in spaces:
221 | output(content[i])
222 | i = i + 1
223 | continue
224 | output('`')
225 | i = i + 1
226 | continue
227 | if char == '\"':
228 | output('\"')
229 | i += 1
230 | memo = 2
231 | continue
232 | if char == '\'':
233 | output('\'')
234 | i += 1
235 | memo = 3
236 | continue
237 | output(char)
238 | elif memo == 1: # 注释中
239 | if word == '*/':
240 | output('``')
241 | i += 2
242 | memo = 0
243 | continue
244 | if char in spaces:
245 | output(content[i])
246 | i += 1
247 | continue
248 | output('`')
249 | elif memo == 2: # 字符串中
250 | if word == '\\\"':
251 | output('$$')
252 | i += 2
253 | continue
254 | if word == '\\\\':
255 | output('$$')
256 | i += 2
257 | continue
258 | if char == '\"':
259 | output('\"')
260 | i += 1
261 | memo = 0
262 | continue
263 | if char in spaces:
264 | output(char)
265 | i += 1
266 | continue
267 | output('$')
268 | elif memo == 3: # 字符中
269 | if word == '\\\'':
270 | output('$$')
271 | i += 2
272 | continue
273 | if word == '\\\\':
274 | output('$$')
275 | i += 2
276 | continue
277 | if char == '\'':
278 | output('\'')
279 | i += 1
280 | memo = 0
281 | continue
282 | if char in spaces:
283 | output(char)
284 | i += 1
285 | continue
286 | output('$')
287 | i += 1
288 | srctext.truncate()
289 | return srctext.getvalue()
290 |
291 | # 查找单一文件的头文件引用情况
292 | def search_reference(self, source, heads):
293 | content = ''
294 | del heads[:]
295 | try:
296 | content = posix.load_file_text(source)
297 | except:
298 | return ''
299 |
300 | content = '\n'.join([ line.strip('\r\n') for line in content.split("\n") ])
301 |
302 | srctext = self.preprocess(content)
303 |
304 | length = len(srctext)
305 | start = 0
306 | endup = -1
307 | number = 0
308 |
309 | while (start >= 0) and (start < length):
310 | start = endup + 1
311 | endup = srctext.find('\n', start)
312 | if (endup < 0):
313 | endup = length
314 | number = number + 1
315 |
316 | offset1 = srctext.find('#', start, endup)
317 | if offset1 < 0: continue
318 | offset2 = srctext.find('include', offset1, endup)
319 | if offset2 < 0: continue
320 | offset3 = srctext.find('\"', offset2, endup)
321 | if offset3 < 0: continue
322 | offset4 = srctext.find('\"', offset3 + 1, endup)
323 | if offset4 < 0: continue
324 |
325 | check_range = [ i for i in range(start, offset1) ]
326 | check_range += [ i for i in range(offset1 + 1, offset2) ]
327 | check_range += [ i for i in range(offset2 + 7, offset3) ]
328 | check = 1
329 |
330 | for i in check_range:
331 | if not (srctext[i] in (' ', '`')):
332 | check = 0
333 | break
334 |
335 | if check != 1:
336 | continue
337 |
338 | name = content[offset3 + 1:offset4]
339 | heads.append([name, offset1, offset4, number])
340 |
341 | return content
342 |
343 | # 合并引用的所有头文件,并返回文件依赖,及找不到的头文件
344 | def parse_source(self, filename, history_headers, lost_headers):
345 | headers = []
346 | filename = os.path.abspath(filename)
347 |
348 | outtext = cio.StringIO()
349 | if not os.path.exists(filename):
350 | sys.stderr.write('can not open %s\n'%(filename))
351 | return outtext.getvalue()
352 | if filename in self._references:
353 | content, headers = self._references[filename]
354 | else:
355 | content = self.search_reference(filename, headers)
356 | self._references[filename] = content, headers
357 | save_cwd = os.getcwd()
358 | file_cwd = os.path.dirname(filename)
359 | if file_cwd == '':
360 | file_cwd = '.'
361 | os.chdir(file_cwd)
362 | available = []
363 | for head in headers:
364 | if os.path.exists(head[0]):
365 | available.append(head)
366 | headers = available
367 | offset = 0
368 | for head in headers:
369 | name = os.path.abspath(os.path.normcase(head[0]))
370 | if not (name in history_headers):
371 | history_headers.append(name)
372 | position = len(history_headers) - 1
373 | text = self.parse_source(name, history_headers, lost_headers)
374 | del history_headers[position]
375 | history_headers.append(name)
376 | outtext.write(content[offset:head[1]] + '\n')
377 | outtext.write('/*:: <%s> ::*/\n'%(head[0]))
378 | outtext.write(text + '\n/*:: ::*/\n'%(head[0]))
379 | offset = head[2] + 1
380 | else:
381 | outtext.write(content[offset:head[1]] + '\n')
382 | outtext.write('/*:: skip including "%s" ::*/\n'%(head[0]))
383 | offset = head[2] + 1
384 | outtext.write(content[offset:])
385 | os.chdir(save_cwd)
386 | return outtext.getvalue()
387 |
388 | # 过滤代码注释
389 | def cleanup_memo (self, text):
390 | content = text
391 | outtext = ''
392 | srctext = self.preprocess(content)
393 | space = ( ' ', '\t', '`' )
394 | start = 0
395 | endup = -1
396 | sized = len(srctext)
397 | while (start >= 0) and (start < sized):
398 | start = endup + 1
399 | endup = srctext.find('\n', start)
400 | if endup < 0:
401 | endup = sized
402 | empty = 1
403 | memod = 0
404 | for i in range(start, endup):
405 | if not (srctext[i] in space):
406 | empty = 0
407 | if srctext[i] == '`':
408 | memod = 1
409 | if empty and memod:
410 | continue
411 | for i in range(start, endup):
412 | if srctext[i] != '`':
413 | outtext = outtext + content[i]
414 | outtext = outtext + '\n'
415 | return outtext
416 |
417 | # 复位依赖关系
418 | def reset (self):
419 | self._references = {}
420 | return 0
421 |
422 | # 直接返回依赖
423 | def dependence (self, filename, reset = False):
424 | head = []
425 | lost = []
426 | if reset: self.reset()
427 | text = self.parse_source(filename, head, lost)
428 | return head, lost, text
429 |
430 | # 查询 Java的信息,返回:(package, imports, classname)
431 | def java_preprocess (self, text):
432 | text = self.preprocess(text)
433 | content = text.replace('\r', '')
434 | p1 = content.find('{')
435 | p2 = content.rfind('}')
436 | if p1 >= 0:
437 | if p2 < 0:
438 | p2 = len(content)
439 | content = content[:p1] + ';\n' + content[p2 + 1:]
440 | content = self.cleanup_memo(content).rstrip() + '\n'
441 | info = { 'package': None, 'import': [], 'class': None }
442 | for line in content.split(';'):
443 | line = line.replace('\n', ' ').strip()
444 | data = [ n.strip() for n in line.split() ]
445 | if len(data) < 2: continue
446 | name = data[0]
447 | if name == 'package':
448 | info['package'] = ''.join(data[1:])
449 | elif name == 'import':
450 | info['import'] += [''.join(data[1:])]
451 | elif 'class' in data or 'interface' in data:
452 | if 'extends' in data:
453 | p = data.index('extends')
454 | data = data[:p]
455 | if 'implements' in data:
456 | p = data.index('implements')
457 | data = data[:p]
458 | info['class'] = data[-1]
459 | return info['package'], info['import'], info['class']
460 |
461 | # returns: (package, imports, classname, srcpath)
462 | def java_parse (self, filename):
463 | try:
464 | text = open(filename).read()
465 | except:
466 | return None, None, None, None
467 | package, imports, classname = self.java_preprocess(text)
468 | if package is None:
469 | path = os.path.dirname(filename)
470 | return None, imports, classname, os.path.abspath(path)
471 | path = os.path.abspath(os.path.dirname(filename))
472 | if sys.platform[:3] == 'win':
473 | path = path.replace('\\', '/')
474 | names = package.split('.')
475 | root = path
476 | srcpath = None
477 | if sys.platform[:3] == 'win':
478 | root = root.lower()
479 | names = [n.lower() for n in names]
480 | while 1:
481 | part = os.path.split(root)
482 | name = names[-1]
483 | names = names[:-1]
484 | if name != part[1]:
485 | break
486 | if len(names) == 0:
487 | srcpath = part[0]
488 | break
489 | if root == part[0]:
490 | break
491 | root = part[0]
492 | return package, imports, classname, srcpath
493 |
494 |
495 | #----------------------------------------------------------------------
496 | # execute and capture
497 | #----------------------------------------------------------------------
498 | def execute(args, shell = False, capture = False):
499 | import sys, os
500 | parameters = []
501 | cmd = None
502 | os.shell_return = -1
503 | if not isinstance(args, list):
504 | import shlex
505 | cmd = args
506 | if sys.platform[:3] == 'win':
507 | ucs = False
508 | if sys.version_info[0] < 3:
509 | if not isinstance(cmd, str):
510 | cmd = cmd.encode('utf-8')
511 | ucs = True
512 | args = shlex.split(cmd.replace('\\', '\x00'))
513 | args = [ n.replace('\x00', '\\') for n in args ]
514 | if ucs:
515 | args = [ n.decode('utf-8') for n in args ]
516 | else:
517 | args = shlex.split(cmd)
518 | for n in args:
519 | if sys.platform[:3] != 'win':
520 | replace = { ' ':'\\ ', '\\':'\\\\', '\"':'\\\"', '\t':'\\t',
521 | '\n':'\\n', '\r':'\\r' }
522 | text = ''.join([ replace.get(ch, ch) for ch in n ])
523 | parameters.append(text)
524 | else:
525 | if (' ' in n) or ('\t' in n) or ('"' in n):
526 | parameters.append('"%s"'%(n.replace('"', ' ')))
527 | else:
528 | parameters.append(n)
529 | if cmd is None:
530 | cmd = ' '.join(parameters)
531 | if sys.platform[:3] == 'win' and len(cmd) > 255:
532 | shell = False
533 | if shell and (not capture):
534 | os.shell_return = os.system(cmd)
535 | return b''
536 | elif (not shell) and (not capture):
537 | import subprocess
538 | if 'call' in subprocess.__dict__:
539 | os.shell_return = subprocess.call(args)
540 | return b''
541 | import subprocess
542 | if 'Popen' in subprocess.__dict__:
543 | p = subprocess.Popen(args, shell = shell,
544 | stdin = subprocess.PIPE, stdout = subprocess.PIPE,
545 | stderr = subprocess.STDOUT)
546 | stdin, stdouterr = (p.stdin, p.stdout)
547 | else:
548 | p = None
549 | stdin, stdouterr = os.popen4(cmd)
550 | stdin.close()
551 | text = stdouterr.read()
552 | stdouterr.close()
553 | if p: p.wait()
554 | os.shell_return = -1
555 | if 'returncode' in p.__dict__:
556 | os.shell_return = p.returncode
557 | if not capture:
558 | sys.stdout.write(text)
559 | sys.stdout.flush()
560 | return b''
561 | return text
562 |
563 |
564 |
565 | #----------------------------------------------------------------------
566 | # Default CFG File
567 | #----------------------------------------------------------------------
568 | ININAME = ''
569 | INIPATH = ''
570 |
571 | CFG = {'abspath':False, 'verbose':False, 'silent':False}
572 |
573 |
574 | #----------------------------------------------------------------------
575 | # configure: 确定gcc位置并从配置读出默认设置
576 | #----------------------------------------------------------------------
577 | class configure(object):
578 |
579 | # 构造函数
580 | def __init__ (self, ininame = ''):
581 | self.dirpath = os.path.split(os.path.abspath(__file__))[0]
582 | self.current = os.getcwd()
583 | if not ininame:
584 | # pylint: disable=simplify-boolean-expression
585 | ininame = ININAME and ININAME or 'emake.ini'
586 | self.ininame = ininame
587 | self.inipath = os.path.join(self.dirpath, self.ininame)
588 | self.iniload = ''
589 | self.haveini = False
590 | self.dirhome = ''
591 | self.target = ''
592 | self.config = {}
593 | self.cp = configparser.ConfigParser()
594 | self.unix = 1
595 | self.xlink = 1
596 | self.searchdirs = None
597 | self.environ = {}
598 | self.exename = {}
599 | self.replace = {}
600 | self.cygwin = ''
601 | for n in os.environ:
602 | self.environ[n] = os.environ[n]
603 | if sys.platform[:3] == 'win':
604 | self.unix = 0
605 | self.GetShortPathName = None
606 | if sys.platform[:6] == 'darwin':
607 | self.xlink = 0
608 | if sys.platform[:3] == 'aix':
609 | self.xlink = 0
610 | self.cpus = 0
611 | self.inited = False
612 | self.fpic = 0
613 | self.name = {}
614 | ext = ('.c', '.cpp', '.c', '.cc', '.cxx', '.s', '.asm', '.m', '.mm')
615 | self.extnames = ext
616 | self.__jdk_home = None
617 | self.profile = None
618 | self.reset()
619 |
620 | # reset all configure
621 | def reset (self):
622 | self.inc = {} # include paths
623 | self.lib = {} # lib paths
624 | self.flag = {} # compile flags
625 | self.pdef = {} # predefined macros
626 | self.link = {} # libraries
627 | self.flnk = {} # link flags
628 | self.wlnk = {} # link pass
629 | self.cond = {} # condition flags
630 | self.param_build = ''
631 | self.param_compile = ''
632 | return 0
633 |
634 | # initialize environment for command tool
635 | def _cmdline_init (self, envname):
636 | config = self._env_config('environ:' + envname)
637 | # print('config', config)
638 | output = []
639 | sep = self.unix and ':' or ';'
640 | PATH = config.get('PATH', '').strip().replace(';', ',')
641 | if self.unix:
642 | PATH = PATH.replace(':', ',')
643 | envpath = PATH + sep + self.environ.get('PATH', '')
644 | for path in envpath.split(sep):
645 | if path.strip('\r\n\t ') == '':
646 | continue
647 | path = os.path.abspath(path)
648 | if os.path.exists(path):
649 | if path not in output:
650 | output.append(path)
651 | config['PATH'] = sep.join(output)
652 | for n in config:
653 | v = config[n]
654 | if n.strip():
655 | os.environ[n] = v
656 | os.environ['PATH'] = config['PATH']
657 | return 0
658 |
659 | # environment configuration for cmdline tool
660 | def _env_config (self, section):
661 | config = {}
662 | if section in self.config:
663 | for n in self.config[section]:
664 | config[n.upper()] = self.config[section][n]
665 | replace = {}
666 | replace['$(INIROOT)'] = os.path.dirname(self.iniload)
667 | replace['$(INIPATH)'] = os.path.abspath(self.iniload)
668 | replace['$(TARGET)'] = self.target
669 | for n in config:
670 | text = config[n]
671 | for key in replace:
672 | text = text.replace(key, replace[key])
673 | config[n] = text
674 | for n in config:
675 | config[n] = self._expand(config, self.environ, n)
676 | return config
677 |
678 | # expand macro
679 | def _expand (self, section, environ, item, d = 0):
680 | if not environ: environ = {}
681 | if not section: section = {}
682 | text = ''
683 | if item in environ:
684 | text = environ[item]
685 | if item in section:
686 | text = section[item]
687 | if d >= 20: return text
688 | names = {}
689 | index = 0
690 | # print('expanding', item)
691 | while 1:
692 | index = text.find('$(', index)
693 | if index < 0: break
694 | p2 = text.find(')', index)
695 | if p2 < 0: break
696 | name = text[index + 2:p2]
697 | index = p2 + 1
698 | names[name] = name.upper()
699 | for name in names:
700 | if name != item:
701 | value = self._expand(section, environ, name.upper(), d + 1)
702 | elif name in environ:
703 | value = environ[name]
704 | else:
705 | value = ''
706 | text = text.replace('$(' + name + ')', value)
707 | names[name] = value
708 | # print('>', text)
709 | return text
710 |
711 | # calculate short path name
712 | def pathshort (self, path):
713 | path = os.path.abspath(path)
714 | if self.unix:
715 | return path
716 | if not self.GetShortPathName:
717 | self.kernel32 = None
718 | self.textdata = None
719 | try:
720 | import ctypes
721 | self.kernel32 = ctypes.windll.LoadLibrary("kernel32.dll")
722 | self.textdata = ctypes.create_string_buffer('\000' * 1024)
723 | self.GetShortPathName = self.kernel32.GetShortPathNameA
724 | args = [ ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int ]
725 | self.GetShortPathName.argtypes = args
726 | self.GetShortPathName.restype = ctypes.c_uint32
727 | except: pass
728 | if not self.GetShortPathName:
729 | return path
730 | retval = self.GetShortPathName(path, self.textdata, 1024)
731 | shortpath = self.textdata.value
732 | if retval <= 0:
733 | return ''
734 | return shortpath
735 |
736 | # read ini files
737 | def _readini (self, inipath):
738 | if '~' in inipath:
739 | inipath = os.path.expanduser(inipath)
740 | if os.path.exists(inipath):
741 | self.iniload = os.path.abspath(inipath)
742 | config = {}
743 | self.cp = posix.load_ini(inipath)
744 | if self.cp:
745 | for sect in self.cp:
746 | for key, val in self.cp[sect].items():
747 | lowsect, lowkey = sect.lower(), key.lower()
748 | self.config.setdefault(lowsect, {})[lowkey] = val
749 | config.setdefault(lowsect, {})[lowkey] = val
750 | self.config['default'] = self.config.get('default', {})
751 | config['default'] = config.get('default', {})
752 | inihome = os.path.abspath(os.path.split(inipath)[0])
753 | dirhome = config['default'].get('home', '')
754 | if dirhome:
755 | dirhome = os.path.join(inihome, dirhome)
756 | if not os.path.exists(dirhome):
757 | sys.stderr.write('error: %s: %s not exists\n'%(inipath, dirhome))
758 | sys.stderr.flush()
759 | else:
760 | self.config['default']['home'] = dirhome
761 | for exename in ('gcc', 'ld', 'ar', 'as', 'nasm', 'yasm', 'dllwrap'):
762 | if exename not in config['default']:
763 | continue
764 | self.exename[exename] = config['default'][exename]
765 | for exename in ('pkg-config', 'pkgconfig'):
766 | if exename not in config['default']:
767 | continue
768 | self.exename['pkgconfig'] = config['default'][exename]
769 | for bp in ('include', 'lib'):
770 | if bp not in config['default']:
771 | continue
772 | data = []
773 | for n in config['default'][bp].replace(';', ',').split(','):
774 | n = os.path.normpath(os.path.join(inihome, self.pathconf(n)))
775 | if not self.unix: n = n.replace('\\', '/')
776 | data.append("'" + n + "'")
777 | text = ','.join(data)
778 | config['default'][bp] = text
779 | self.config['default'][bp] = text
780 | java = config['default'].get('java', '')
781 | if java:
782 | java = os.path.join(inihome, java)
783 | if not os.path.exists(java):
784 | sys.stderr.write('error: %s: %s not exists\n'%(inipath, java))
785 | sys.stderr.flush()
786 | else:
787 | self.config['default']['java'] = os.path.abspath(java)
788 | if 'xlink' in config['default']:
789 | xlink = config['default']['xlink'].strip().lower()
790 | if xlink in ('true', '1', 'yes', 'on', 'y', 't'):
791 | self.xlink = 1
792 | elif xlink in ('false', 'f', '0', 'no', 'off', 'n'):
793 | self.xlink = 0
794 | if 'option' in config['default']:
795 | option = config['default']['option'].strip()
796 | if 'x' in option:
797 | self.xlink = 0
798 | elif 'X' in option:
799 | self.xlink = 1
800 | self.haveini = True
801 | return 0
802 |
803 | # check dirhome
804 | def check (self):
805 | if not self.dirhome:
806 | sys.stderr.write('error: cannot find gcc home in config\n')
807 | sys.stderr.flush()
808 | sys.exit(1)
809 | return 0
810 |
811 | # init configure
812 | def init (self):
813 | if self.inited:
814 | return 0
815 | self.config = {}
816 | self.reset()
817 | fn = INIPATH
818 | self.iniload = os.path.abspath(self.inipath)
819 | if fn:
820 | if os.path.exists(fn):
821 | self._readini(fn)
822 | self.iniload = os.path.abspath(fn)
823 | else:
824 | sys.stderr.write('error: cannot open %s\n'%fn)
825 | sys.stderr.flush()
826 | sys.exit(1)
827 | else:
828 | if self.unix:
829 | self._readini('/etc/%s'%self.ininame)
830 | self._readini('/usr/local/etc/%s'%self.ininame)
831 | self._readini('~/.config/%s'%self.ininame)
832 | self._readini(self.inipath)
833 | self.dirhome = self._getitem('default', 'home', '')
834 | cfghome = self.dirhome
835 | if not self.haveini:
836 | #sys.stderr.write('warning: %s cannot be open\n'%(self.ininame))
837 | sys.stderr.flush()
838 | defined = self.exename.get('gcc', None) and True or False
839 | for name in ('gcc', 'ar', 'ld', 'as', 'nasm', 'yasm', 'dllwrap'):
840 | exename = self.exename.get(name, name)
841 | if not self.unix:
842 | elements = list(os.path.splitext(exename)) + ['', '']
843 | if not elements[1]: exename = elements[0] + '.exe'
844 | self.exename[name] = exename
845 | gcc = self.exename['gcc']
846 | p1 = os.path.join(self.dirhome, '%s.exe'%gcc)
847 | p2 = os.path.join(self.dirhome, '%s'%gcc)
848 | if (not os.path.exists(p1)) and (not os.path.exists(p2)):
849 | self.dirhome = ''
850 | if sys.platform[:3] != 'win':
851 | if self.dirhome[1:2] == ':':
852 | self.dirhome = ''
853 | if (not self.dirhome) and (not cfghome):
854 | self.dirhome = self.__search_gcc()
855 | if (not self.dirhome) and (not defined):
856 | gcc = 'clang'
857 | self.exename['gcc'] = gcc
858 | self.dirhome = self.__search_gcc()
859 | if self.dirhome:
860 | self.dirhome = os.path.abspath(self.dirhome)
861 | try:
862 | cpus = self._getitem('default', 'cpu', '')
863 | intval = int(cpus)
864 | self.cpus = intval
865 | except:
866 | pass
867 | cygwin = self._getitem('default', 'cygwin')
868 | self.cygwin = ''
869 | if cygwin and (not self.unix):
870 | if os.path.exists(cygwin):
871 | cygwin = os.path.abspath(cygwin)
872 | bash = os.path.join(cygwin, 'bin/bash.exe')
873 | if os.path.exists(bash):
874 | self.cygwin = cygwin
875 | self.name = {}
876 | self.name[sys.platform.lower()] = 1
877 | if sys.platform[:3] == 'win':
878 | self.name['win'] = 1
879 | if sys.platform[:7] == 'freebsd':
880 | self.name['freebsd'] = 1
881 | self.name['unix'] = 1
882 | if sys.platform[:5] == 'linux':
883 | self.name['linux'] = 1
884 | self.name['unix'] = 1
885 | if sys.platform[:6] == 'darwin':
886 | self.name['darwin'] = 1
887 | self.name['unix'] = 1
888 | if sys.platform == 'cygwin':
889 | self.name['unix'] = 1
890 | if sys.platform[:5] == 'sunos':
891 | self.name['sunos'] = 1
892 | if os.name == 'posix':
893 | self.name['unix'] = 1
894 | if os.name == 'nt':
895 | self.name['win'] = 1
896 | if 'win' in self.name:
897 | self.name['nt'] = 1
898 | self.target = self._getitem('default', 'target').strip()
899 | if not self.target:
900 | self.target = sys.platform
901 | self.name[self.target] = 1
902 | names = self._getitem('default', 'name').strip()
903 | if names:
904 | self.name = {}
905 | for name in names.replace(';', ',').split(','):
906 | name = name.strip('\r\n\t ').lower()
907 | if not name: continue
908 | self.name[name] = 1
909 | if sys.platform[:3] in ('win', 'cyg'):
910 | self.fpic = False
911 | else:
912 | self.fpic = True
913 | #self.__python_config()
914 | self.replace = {}
915 | self.replace['home'] = self.dirhome
916 | self.replace['emake'] = self.dirpath
917 | self.replace['inihome'] = os.path.dirname(self.iniload)
918 | self.replace['inipath'] = self.inipath
919 | self.replace['target'] = self.target
920 | self.replace['profile'] = self.profile
921 | self._reset_path()
922 | self._reset_environ()
923 | self.inited = True
924 | return 0
925 |
926 | # read configuration
927 | def _getitem (self, sect, key, default = ''):
928 | return self.config.get(sect, {}).get(key, default)
929 |
930 | # reset $PATH
931 | def _reset_path (self):
932 | PATH = self._getitem('default', 'path', '').strip()
933 | if not PATH:
934 | return -1
935 | sep = self.unix and ':' or ';'
936 | pathout = []
937 | PATH = PATH.replace(';', ',')
938 | if self.unix:
939 | PATH = PATH.replace(':', ',')
940 | for path in PATH.split(','):
941 | if os.path.isabs(path):
942 | pathout.append(path)
943 | elif os.path.exists(self.inipath):
944 | inihome = os.path.dirname(os.path.abspath(self.inipath))
945 | t = os.path.join(inihome, path)
946 | pathout.append(t)
947 | if 'PATH' not in CFG:
948 | CFG['PATH'] = os.environ.get('PATH', '')
949 | self._origin_os_path = CFG['PATH']
950 | finalize = []
951 | for path in pathout:
952 | finalize.append(path)
953 | t = sep.join(finalize)
954 | self._new_os_path = t + sep + os.environ.get('PATH', '')
955 | os.environ['PATH'] = self._new_os_path
956 | return 0
957 |
958 | # reset environment variables
959 | def _reset_environ (self):
960 | PKG_CONFIG_PATH = self._getitem('default', 'pcpath', '').strip()
961 | if PKG_CONFIG_PATH:
962 | os.environ['PKG_CONFIG_PATH'] = PKG_CONFIG_PATH
963 | ENVIRON = self._getitem('default', 'environ', '').strip()
964 | if ENVIRON:
965 | for item in ENVIRON.split(','):
966 | if not item: continue
967 | name, value = (item.split('=') + ['', ''])[:2]
968 | name, value = name.strip(), value.strip()
969 | if name:
970 | os.environ[name] = value
971 | return 0
972 |
973 | # 取得替换了$(HOME)变量的路径
974 | def path (self, path):
975 | path = path.replace('$(HOME)', self.dirhome).replace('\\', '/')
976 | path = self.cygpath(path)
977 | text = ''
978 | issep = False
979 | for n in path:
980 | if n == '/':
981 | if issep is False: text += n
982 | issep = True
983 | else:
984 | text += n
985 | issep = False
986 | return os.path.abspath(text)
987 |
988 | # 取得可用于参数的文本路径
989 | def pathtext (self, name):
990 | name = os.path.normpath(name)
991 | name = self.cygpath(name)
992 | name = name.replace('"', '""')
993 | if ' ' in name:
994 | return '"%s"'%(name)
995 | if self.unix:
996 | name = name.replace('\\', '/')
997 | return name
998 |
999 | # 取得短路径:当前路径的相对路径
1000 | def relpath (self, name, start = None):
1001 | name = os.path.abspath(name)
1002 | if not start:
1003 | start = os.getcwd()
1004 | if 'relpath' in os.path.__dict__:
1005 | try:
1006 | return os.path.relpath(name, start)
1007 | except:
1008 | pass
1009 | current = start.replace('\\', '/')
1010 | if len(current) > 0:
1011 | if current[-1] != '/':
1012 | current += '/'
1013 | name = self.path(name).replace('\\', '/')
1014 | size = len(current)
1015 | if self.unix:
1016 | if name[:size] == current:
1017 | name = name[size:]
1018 | else:
1019 | if name[:size].lower() == current.lower():
1020 | name = name[size:]
1021 | return name
1022 |
1023 | # 取得短路径:当前路径的相对路径
1024 | def pathrel (self, name, start = None):
1025 | return self.pathtext(self.relpath(name, start))
1026 |
1027 | # 转换到cygwin路径
1028 | def cygpath (self, path):
1029 | if self.unix and path[1:2] == ':':
1030 | path = '/cygdrive/%s%s'%(path[0], path[2:].replace('\\', '/'))
1031 | return path
1032 |
1033 | # 转换到cygwin路径
1034 | def win2cyg (self, path):
1035 | path = os.path.abspath(path)
1036 | return '/cygdrive/%s%s'%(path[0], path[2:].replace('\\', '/'))
1037 |
1038 | # 转换回cygwin路径
1039 | def cyg2win (self, path):
1040 | if path[1:2] == ':':
1041 | return os.path.abspath(path)
1042 | if path.lower().startswith('/cygdrive/'):
1043 | path = path[10] + ':' + path[11:]
1044 | return os.path.abspath(path)
1045 | if not path.startswith('/'):
1046 | raise Exception('cannot convert path: %s'%path)
1047 | if not self.cygwin:
1048 | raise Exception('cannot find cygwin root')
1049 | return os.path.abspath(os.path.join(self.cygwin, path[1:]))
1050 |
1051 | # 添加头文件目录
1052 | def push_inc (self, inc):
1053 | path = self.path(inc)
1054 | if not os.path.exists(path):
1055 | sys.stderr.write('warning: ignore invalid path %s\n'%path)
1056 | return -1
1057 | path = self.pathtext(path)
1058 | self.inc[path] = 1
1059 | return 0
1060 |
1061 | # 添加库文件目录
1062 | def push_lib (self, lib):
1063 | path = self.path(lib)
1064 | if not os.path.exists(path):
1065 | sys.stderr.write('warning: ignore invalid path %s\n'%path)
1066 | return -1
1067 | path = self.pathtext(path)
1068 | self.lib[path] = 1
1069 | return 0
1070 |
1071 | # 添加参数
1072 | def push_flag (self, flag):
1073 | if flag not in self.flag:
1074 | self.flag[flag] = len(self.flag)
1075 | return 0
1076 |
1077 | # 添加链接库
1078 | def push_link (self, link):
1079 | if link[-2:].lower() in ('.o', '.a'):
1080 | link = self.pathtext(self.path(link))
1081 | else:
1082 | link = '-l%s'%link.replace(' ', '_')
1083 | if link not in self.link:
1084 | self.link[link] = len(self.link)
1085 | #print('push: ' + link)
1086 | return 0
1087 |
1088 | # 添加预定义
1089 | def push_pdef (self, define):
1090 | self.pdef[define] = 1
1091 |
1092 | # 添加连接参数
1093 | def push_flnk (self, flnk):
1094 | if flnk not in self.flnk:
1095 | self.flnk[flnk] = len(self.flnk)
1096 | return 0
1097 |
1098 | # 添加链接传递
1099 | def push_wlnk (self, wlnk):
1100 | if wlnk not in self.wlnk:
1101 | self.wlnk[wlnk] = len(self.wlnk)
1102 | return 0
1103 |
1104 | # 添加条件参数
1105 | def push_cond (self, flag, condition):
1106 | key = (flag, condition)
1107 | if key not in self.cond:
1108 | self.cond[key] = len(self.cond)
1109 | return 0
1110 |
1111 | # 搜索gcc
1112 | def __search_gcc (self):
1113 | dirpath = self.dirpath
1114 | gcc = self.exename['gcc']
1115 | splitter = self.unix and ':' or ';'
1116 | if os.path.exists(os.path.join(dirpath, '%s'%gcc)):
1117 | return os.path.abspath(dirpath)
1118 | if os.path.exists(os.path.join(dirpath, 'bin/%s'%gcc)):
1119 | return os.path.abspath(os.path.join(dirpath, 'bin'))
1120 | for d in os.environ.get('PATH', '').split(splitter):
1121 | n = os.path.abspath(os.path.join(d, '%s'%gcc))
1122 | if os.path.exists(n): return os.path.abspath(d)
1123 | if self.unix:
1124 | if os.path.exists('/bin/%s'%gcc):
1125 | return '/bin'
1126 | if os.path.exists('/usr/bin/%s'%gcc):
1127 | return '/usr/bin'
1128 | if os.path.exists('/usr/local/bin/%s'%gcc):
1129 | return '/usr/local/bin'
1130 | if os.path.exists('/opt/bin/%s'%gcc):
1131 | return '/opt/bin'
1132 | if os.path.exists('/opt/usr/bin/%s'%gcc):
1133 | return '/opt/usr/bin'
1134 | if os.path.exists('/opt/usr/local/bin/%s'%gcc):
1135 | return '/opt/usr/local/bin'
1136 | return ''
1137 |
1138 | # 写默认的配置文件
1139 | def _write_default_ini (self):
1140 | default = ''' [default]
1141 | include=$(HOME)/include
1142 | lib=$(HOME)/lib
1143 | '''
1144 | text = '\n'.join([ n.strip('\t\r\n ') for n in default.split('\n') ])
1145 | if os.path.exists(self.inipath):
1146 | return -1
1147 | fp = open(self.inipath, 'w')
1148 | fp.write(text)
1149 | fp.close()
1150 | return 0
1151 |
1152 | # 配置路径
1153 | def pathconf (self, path):
1154 | path = path.strip(' \t\r\n')
1155 | if path[:1] == '\'' and path[-1:] == '\'': path = path[1:-1]
1156 | if path[:1] == '\"' and path[-1:] == '\"': path = path[1:-1]
1157 | return path.strip(' \r\n\t')
1158 |
1159 | # _getitem with profile
1160 | def __get_config (self, section, keyname):
1161 | value = self._getitem(section, keyname, '')
1162 | if self.profile:
1163 | keyopt = '%s@%s'%(keyname, self.profile)
1164 | valueopt = self._getitem(section, keyopt, '')
1165 | if valueopt:
1166 | value += ',' + valueopt
1167 | return value
1168 |
1169 | # 刷新配置
1170 | def loadcfg (self, profile, sect = 'default', reset = True):
1171 | self.init()
1172 | if reset: self.reset()
1173 | self.profile = profile
1174 | # f1 = lambda n: (n[:1] != '\'' or n[-1:] != '\'') and n
1175 | config = lambda n: self.__get_config(sect, n)
1176 | for path in config('include').replace(';', ',').split(','):
1177 | path = self.pathconf(path)
1178 | if not path: continue
1179 | self.push_inc(path)
1180 | for path in config('lib').replace(';', ',').split(','):
1181 | path = self.pathconf(path)
1182 | if not path: continue
1183 | self.push_lib(path)
1184 | for link in config('link').replace(';', ',').split(','):
1185 | link = self.pathconf(link)
1186 | if not link: continue
1187 | self.push_link(link)
1188 | for flag in config('flag').replace(';', ',').split(','):
1189 | flag = flag.strip(' \t\r\n')
1190 | if not flag: continue
1191 | self.push_flag(flag)
1192 | for pdef in config('define').replace(';', ',').split(','):
1193 | pdef = pdef.strip(' \t\r\n')
1194 | if not pdef: continue
1195 | self.push_pdef(pdef.replace(' ', '_'))
1196 | for flnk in config('flnk').replace(';', ',').split(','):
1197 | flnk = flnk.strip(' \t\r\n')
1198 | if not flnk: continue
1199 | self.push_flnk(flnk)
1200 | for wlnk in config('wlnk').replace(';', ',').split(','):
1201 | wlnk = wlnk.strip(' \t\r\n')
1202 | if not wlnk: continue
1203 | self.push_wlnk(wlnk)
1204 | for name in ('cflag', 'cxxflag', 'mflag', 'mmflag', 'sflag'):
1205 | for flag in config(name).replace(';', ',').split(','):
1206 | flag = flag.strip(' \t\r\n')
1207 | if not flag: continue
1208 | self.push_cond(flag, name)
1209 | self.parameters()
1210 | return 0
1211 |
1212 | # 按字典值顺序取出配置
1213 | def sequence (self, data):
1214 | x = [ (n, k) for (k, n) in data.items() ]
1215 | x.sort()
1216 | y = [ n for (k, n) in x ]
1217 | return y
1218 |
1219 | # 替换字符串
1220 | def __replace_key (self, text):
1221 | for key in self.replace:
1222 | value = self.replace[key]
1223 | check = '$(' + key + ')'
1224 | if check in text:
1225 | text = text.replace(check, value)
1226 | return text
1227 |
1228 | # 返回条件参数
1229 | def condition (self, conditions):
1230 | flags = []
1231 | for flag, cond in self.sequence(self.cond):
1232 | if cond in conditions:
1233 | flags.append(flag)
1234 | return flags
1235 |
1236 | # 返回序列化的参数
1237 | def parameters (self):
1238 | text = ''
1239 | for inc in self.sequence(self.inc):
1240 | text += '-I%s '%inc
1241 | for lib in self.sequence(self.lib):
1242 | text += '-L%s '%lib
1243 | for flag in self.sequence(self.flag):
1244 | text += '%s '%self.__replace_key(flag)
1245 | for pdef in self.sequence(self.pdef):
1246 | text += '-D%s '%pdef
1247 | self.param_compile = text.strip(' ')
1248 | text = ''
1249 | if self.xlink:
1250 | text = '-Xlinker "-(" '
1251 | for link in self.sequence(self.link):
1252 | text += '%s '%self.__replace_key(link)
1253 | if self.xlink:
1254 | text += ' -Xlinker "-)"'
1255 | else:
1256 | text = text + ' ' + text
1257 | self.param_build = self.param_compile + ' ' + text
1258 | for flnk in self.sequence(self.flnk):
1259 | self.param_build += ' %s'%self.__replace_key(flnk)
1260 | wl = ','.join([ self.__replace_key(n) for n in self.sequence(self.wlnk) ])
1261 | if wl and self.wlnk:
1262 | self.param_build += ' -Wl,' + wl
1263 | return text
1264 |
1265 | # gcc 的search-dirs
1266 | def __searchdirs (self):
1267 | if self.searchdirs is not None:
1268 | return self.searchdirs
1269 | path = os.path.abspath(os.path.join(self.dirhome, 'bin/gcc'))
1270 | if not self.unix:
1271 | name = self.pathshort(path)
1272 | if (not name) and os.path.exists(path + '.exe'):
1273 | name = self.pathshort(path + '.exe')
1274 | if name: path = name
1275 | cmdline = path + ' -print-search-dirs'
1276 | fp = os.popen(cmdline, 'r')
1277 | data = fp.read()
1278 | fp.close()
1279 | fp = None
1280 | body = ''
1281 | for line in data.split('\n'):
1282 | if line[:10] == 'libraries:':
1283 | body = line[10:].strip('\r\n ')
1284 | if body[:1] == '=': body = body[1:]
1285 | break
1286 | part = []
1287 | if sys.platform[:3] == 'win': part = body.split(';')
1288 | else: part = body.split(':')
1289 | data = []
1290 | dict = {}
1291 | for n in part:
1292 | path = os.path.abspath(os.path.normpath(n))
1293 | if path not in dict:
1294 | if os.path.exists(path):
1295 | data.append(path)
1296 | dict[path] = 1
1297 | else:
1298 | dict[path] = 0
1299 | self.searchdirs = data
1300 | return data
1301 |
1302 | # 检测库是否存在
1303 | def checklib (self, name):
1304 | name = 'lib' + name + '.a'
1305 | for n in self.__searchdirs():
1306 | if os.path.exists(os.path.join(n, name)):
1307 | return True
1308 | for n in self.lib:
1309 | if os.path.exists(os.path.join(n, name)):
1310 | return True
1311 | return False
1312 |
1313 | # 取得可执行名称
1314 | def getname (self, binname):
1315 | exename = self.exename.get(binname, binname)
1316 | path = os.path.abspath(os.path.join(self.dirhome, exename))
1317 | if not self.unix:
1318 | name = self.pathshort(path)
1319 | if (not name) and os.path.exists(path + '.exe'):
1320 | name = self.pathshort(path + '.exe')
1321 | if name: path = name
1322 | return path
1323 |
1324 | # execute GNU tools
1325 | def execute (self, binname, parameters, printcmd = False, capture = False):
1326 | path = os.path.abspath(os.path.join(self.dirhome, binname))
1327 | if not self.unix:
1328 | name = self.pathshort(path)
1329 | if (not name) and os.path.exists(path + '.exe'):
1330 | name = self.pathshort(path + '.exe')
1331 | if name: path = name
1332 | cmd = '%s %s'%(self.pathtext(path), parameters)
1333 | #printcmd = True
1334 | text = ''
1335 | if printcmd:
1336 | if not capture: print(cmd)
1337 | else: text = cmd + '\n'
1338 | sys.stdout.flush()
1339 | sys.stderr.flush()
1340 | os.shell_return = -1
1341 | self.shell_return = -1
1342 | output = execute(cmd, shell = False, capture = capture)
1343 | self.shell_return = os.shell_return
1344 | if sys.version_info[0] >= 3:
1345 | if isinstance(output, bytes):
1346 | output = posix.decode_string(output)
1347 | text = text + output
1348 | return text
1349 |
1350 | # execute gcc
1351 | def gcc (self, parameters, needlink, printcmd = False, capture = False):
1352 | param = self.param_build
1353 | if not needlink:
1354 | param = self.param_compile
1355 | parameters = '%s %s'%(parameters, param)
1356 | # printcmd = True
1357 | return self.execute(self.exename['gcc'], parameters, printcmd, capture)
1358 |
1359 | # 根据扩展名取得额外的编译参数
1360 | def cond_flags (self, srcname):
1361 | extname = os.path.splitext(srcname)[-1].lower()
1362 | extname = os.path.splitext(srcname)[-1].lower()
1363 | cond = []
1364 | if extname in ('.c', '.h'):
1365 | cond = self.condition({'cflag':1})
1366 | elif extname in ('.cpp', '.cc', '.cxx', '.hpp', '.hh'):
1367 | cond = self.condition({'cxxflag':1})
1368 | elif extname in ('.s', '.asm'):
1369 | cond = self.condition({'sflag':1})
1370 | elif extname in ('.m',):
1371 | cond = self.condition({'mflag':1})
1372 | elif extname in ('.mm',):
1373 | cond = self.condition({'mmflag':1})
1374 | return cond
1375 |
1376 | # 编译
1377 | def compile (self, srcname, objname, cflags, printcmd = False, capture = False):
1378 | if CFG['abspath']:
1379 | srcname = self.pathtext(os.path.abspath(srcname))
1380 | else:
1381 | srcname = self.pathrel(srcname)
1382 | cmd = '-c %s -o %s %s'%(srcname, self.pathrel(objname), cflags)
1383 | cond = self.cond_flags(srcname)
1384 | if cond:
1385 | cmd = cmd + ' ' + (' '.join(cond))
1386 | return self.gcc(cmd, False, printcmd, capture)
1387 |
1388 | # 取得编译参数
1389 | def mm (self, srcname, cflags):
1390 | if CFG['abspath']:
1391 | srcname = self.pathtext(os.path.abspath(srcname))
1392 | else:
1393 | srcname = self.pathrel(srcname)
1394 | cond = self.cond_flags(srcname)
1395 | if cond:
1396 | cflags = cflags + ' ' + (' '.join(cond))
1397 | cmd = '-MM %s %s'%(srcname, cflags)
1398 | hr = self.gcc(cmd, False, False, True)
1399 | if os.shell_return != 0:
1400 | return None
1401 | return hr
1402 |
1403 | # 使用 dllwrap
1404 | def dllwrap (self, parameters, printcmd = False, capture = False):
1405 | text = ''
1406 | for lib in self.sequence(self.lib):
1407 | text += '-L%s '%lib
1408 | for link in self.sequence(self.link):
1409 | text += '%s '%link
1410 | for flnk in self.sequence(self.flnk):
1411 | text += '%s '%flnk
1412 | parameters = '%s %s'%(parameters, text)
1413 | dllwrap = self.exename.get('dllwrap', 'dllwrap')
1414 | return self.execute(dllwrap, parameters, printcmd, capture)
1415 |
1416 | # call pkg-config
1417 | def pkgconfig (self, parameters, printcmd = False, capture = False):
1418 | pkgconfig = self.exename.get('pkgconfig', 'pkg-config')
1419 | text = self.execute(pkgconfig, parameters, printcmd, capture)
1420 | return self.shell_return, text
1421 |
1422 | # create lib file
1423 | def makelib (self, output, objs = [], printcmd = False, capture = False):
1424 | if 0:
1425 | name = ' '.join([ self.pathrel(n) for n in objs ])
1426 | parameters = 'crv %s %s'%(self.pathrel(output), name)
1427 | return self.execute(self.exename['ar'], parameters, printcmd, capture)
1428 | objs = [ n for n in objs ]
1429 | for link in self.sequence(self.wlnk):
1430 | if link[-2:] in ('.a', '.o'):
1431 | if os.path.exists(link):
1432 | objs.append(link)
1433 | return self.composite(output, objs, printcmd, capture)
1434 |
1435 | # create dynamic library: .so or .dll
1436 | def makedll (self, output, objs = [], param = '', printcmd = False, capture = False):
1437 | if (not param) or (self.unix):
1438 | if sys.platform[:6] == 'darwin':
1439 | param = '-dynamiclib'
1440 | else:
1441 | param = '--shared'
1442 | if self.fpic:
1443 | param += ' -fPIC'
1444 | return self.makeexe(output, objs, param, printcmd, capture)
1445 | else:
1446 | name = ' '.join([ self.pathrel(n) for n in objs ])
1447 | parameters = '%s -o %s %s'%(param,
1448 | self.pathrel(output), name)
1449 | return self.dllwrap(parameters, printcmd, capture)
1450 |
1451 | # create executable file
1452 | def makeexe (self, output, objs = [], param = '', printcmd = False, capture = False):
1453 | name = ' '.join([ self.pathrel(n) for n in objs ])
1454 | if self.xlink:
1455 | name = '-Xlinker "-(" ' + name + ' -Xlinker "-)"'
1456 | parameters = '-o %s %s %s'%(self.pathrel(output), param, name)
1457 | return self.gcc(parameters, True, printcmd, capture)
1458 |
1459 | # merge .o and .a files into new .a file
1460 | def composite (self, output, objs = [], printcmd = False, capture = False):
1461 | import os, tempfile, shutil
1462 | cwd = os.getcwd()
1463 | temp = tempfile.mkdtemp('.int', 'lib')
1464 | output = os.path.abspath(output)
1465 | libname = []
1466 | for name in [ os.path.abspath(n) for n in objs ]:
1467 | if name not in libname:
1468 | libname.append(name)
1469 | outpath = os.path.join(temp, 'out')
1470 | srcpath = os.path.join(temp, 'src')
1471 | os.mkdir(outpath)
1472 | os.mkdir(srcpath)
1473 | os.chdir(srcpath)
1474 | names = {}
1475 | for source in libname:
1476 | os.chdir(srcpath)
1477 | for fn in [ n for n in os.listdir('.') ]:
1478 | os.remove(fn)
1479 | files = []
1480 | filetype = os.path.splitext(source)[-1].lower()
1481 | if filetype == '.o':
1482 | files.append(source)
1483 | else:
1484 | args = '-x %s'%self.pathrel(source)
1485 | self.execute(self.exename['ar'], args, printcmd, capture)
1486 | for fn in os.listdir('.'):
1487 | files.append(os.path.abspath(fn))
1488 | for fn in files:
1489 | name = os.path.split(fn)[-1]
1490 | part = os.path.splitext(name)
1491 | last = None
1492 | for i in range(1000):
1493 | newname = (i > 0) and (part[0] + '_%d'%i + part[1]) or name
1494 | if newname not in names:
1495 | last = newname
1496 | break
1497 | if last and os.path.exists(fn):
1498 | names[last] = 1
1499 | shutil.copyfile(fn, os.path.join(outpath, last))
1500 | os.chdir(outpath)
1501 | argv = ['crv', self.pathrel(output)]
1502 | args = ' '.join(argv + [self.pathrel(n) for n in names])
1503 | try: os.remove(output)
1504 | except: pass
1505 | self.execute(self.exename['ar'], args, printcmd, capture)
1506 | os.chdir(cwd)
1507 | shutil.rmtree(temp)
1508 | return 0
1509 |
1510 | # run tool
1511 | def cmdtool (self, sectname, args, printcmd = False):
1512 | envsave = [ (n, os.environ[n]) for n in os.environ ]
1513 | self._cmdline_init(sectname)
1514 | if isinstance(args, str):
1515 | cmd = args
1516 | elif isinstance(args, list) or isinstance(args, tuple):
1517 | replace = { ' ':'\\ ', '\\':'\\\\', '\"':'\\\"', '\t':'\\t',
1518 | '\n':'\\n', '\r':'\\r' }
1519 | parameters = []
1520 | for n in args:
1521 | if self.unix:
1522 | text = ''.join([ replace.get(ch, ch) for ch in n ])
1523 | parameters.append(text)
1524 | else:
1525 | if (' ' in n) or ('\t' in n) or ('"' in n):
1526 | parameters.append('"%s"'%(n.replace('"', ' ')))
1527 | else:
1528 | parameters.append(n)
1529 | cmd = ' '.join(parameters)
1530 | if printcmd:
1531 | print('>', cmd)
1532 | sys.stdout.flush()
1533 | sys.stderr.flush()
1534 | code = os.system(cmd)
1535 | envflag = {}
1536 | remove = []
1537 | for n, v in envsave:
1538 | os.environ[n] = v
1539 | envflag[n] = True
1540 | for n in os.environ:
1541 | if n not in envflag:
1542 | remove.append(n)
1543 | for n in remove:
1544 | del os.environ[n]
1545 | return code
1546 |
1547 | # 调用 Cygwin Bash
1548 | def cygwin_bash (self, cmds, capture = False):
1549 | import subprocess
1550 | output = ''
1551 | bashpath = self.pathshort(os.path.join(self.cygwin, 'bin/bash.exe'))
1552 | if 'Popen' in subprocess.__dict__:
1553 | args = [ bashpath, '--login' ]
1554 | outmode = capture and subprocess.PIPE or None
1555 | p = subprocess.Popen(args, shell = False,
1556 | stdin = subprocess.PIPE, stdout = outmode,
1557 | stderr = subprocess.STDOUT)
1558 | stdin, stdouterr = (p.stdin, p.stdout)
1559 | stdin.write(cmds + '\nexit\n')
1560 | stdin.flush()
1561 | if capture:
1562 | output = stdouterr.read()
1563 | p.wait()
1564 | else:
1565 | p = None
1566 | stdin, stdouterr = os.popen4('%s --login'%bashpath, 'b')
1567 | stdin.write(cmds + '\nexit\n')
1568 | stdin.flush()
1569 | if not capture:
1570 | while True:
1571 | output = stdouterr.readline()
1572 | if output == '':
1573 | break
1574 | sys.stdout.write(output + '\n')
1575 | sys.stdout.flush()
1576 | else:
1577 | output = stdouterr.read()
1578 | stdin = None
1579 | stdouterr = None
1580 | return output
1581 |
1582 | # 运行 Cygwin 命令行
1583 | def cygwin_execute (self, sect, exename, parameters = '', capture = 0):
1584 | capture = capture and True or False
1585 | sect = 'environ:' + sect.lower()
1586 | home = self.win2cyg(os.getcwd()) # noqa: F841
1587 | cmds = 'export LANG=C\n'
1588 | if sect in self.config:
1589 | for n in self.config[sect]:
1590 | cmds += 'export %s="%s"\n'%(n.upper(), self.config[sect][n])
1591 | cmds += 'cd "%s"\n'%self.win2cyg(os.getcwd())
1592 | if exename:
1593 | exename = self.win2cyg(exename)
1594 | cmds += '"%s" %s\n'%(exename, parameters)
1595 | else:
1596 | cmds += '%s\n'%parameters
1597 | if 0:
1598 | print('-' * 72)
1599 | print(cmds)
1600 | print('-' * 72)
1601 | os.environ['EMAKECYGWIN'] = '1'
1602 | return self.cygwin_bash(cmds, capture)
1603 |
1604 | # 读取连接基准地址
1605 | def readlink (self, fn):
1606 | if not self.unix:
1607 | return fn
1608 | while True:
1609 | try:
1610 | f2 = os.readlink(fn)
1611 | fn = f2
1612 | except:
1613 | break
1614 | return fn
1615 |
1616 | # 搜索 Python开发路径
1617 | def python_config (self):
1618 | cflags = self._getitem('default', 'python_cflags', None)
1619 | ldflags = self._getitem('default', 'python_ldflags', None)
1620 | if cflags or ldflags:
1621 | return (cflags.strip('\r\n\t '), ldflags.strip('\r\n\t '))
1622 | pythoninc, pythonlib = [], []
1623 | import distutils.sysconfig
1624 | sysconfig = distutils.sysconfig
1625 | inc1 = sysconfig.get_python_inc()
1626 | inc2 = sysconfig.get_python_inc(plat_specific = True)
1627 | pythoninc.append('-I' + self.pathtext(inc1))
1628 | if inc2 != inc1:
1629 | pythoninc.append('-I' + self.pathtext(inc2))
1630 | pyver = sysconfig.get_config_var('VERSION')
1631 | getvar = sysconfig.get_config_var
1632 | if not pyver:
1633 | v1, v2 = sys.version_info[:2]
1634 | pyver = self.unix and '%s.%s'%(v1, v2) or '%s%s'%(v1, v2)
1635 | lib1 = getvar('LIBS')
1636 | pythonlib.extend(lib1 and lib1.split() or [])
1637 | prefix = sys.prefix
1638 | if os.path.exists(prefix):
1639 | if not pythoninc:
1640 | n1 = os.path.join(prefix, 'include/python%s'%pyver)
1641 | n2 = os.path.join(prefix, 'include')
1642 | if os.path.exists(n1 + '/Python.h'):
1643 | pythoninc.append('-I' + self.pathtext(n1))
1644 | elif os.path.exists(n2 + '/Python.h'):
1645 | pythoninc.append('-I' + self.pathtext(n2))
1646 | if not pythonlib:
1647 | n1 = os.path.join(prefix, 'lib/python%s'%pyver)
1648 | n2 = os.path.join(n1, 'config')
1649 | n3 = os.path.join(prefix, 'libs')
1650 | fn1 = 'libpython' + pyver + '.a'
1651 | fn2 = 'libpython' + pyver + '.dll.a'
1652 | done = False
1653 | for ff in (fn1, fn2):
1654 | for nn in (n1, n2, n3):
1655 | if os.path.exists(nn + '/' + ff):
1656 | pythonlib.append('-L' + self.pathtext(nn))
1657 | done = True
1658 | break
1659 | if done:
1660 | break
1661 | lib2 = getvar('SYSLIBS')
1662 | pythonlib.extend(lib2 and lib2.split() or [])
1663 | if not getvar('Py_ENABLE_SHARED'):
1664 | if getvar('LIBPL'):
1665 | pythonlib.insert(0, '-L' + getvar('LIBPL'))
1666 | if not getvar('PYTHONFRAMEWORK'):
1667 | if getvar('LINKFORSHARED'):
1668 | pythonlib.extend(getvar('LINKFORSHARED').split())
1669 | pythonlib.append('-lpython' + pyver)
1670 | cflags = ' '.join(pythoninc)
1671 | ldflags = ' '.join(pythonlib)
1672 | return cflags, ldflags
1673 |
1674 | # 最终完成 java配置
1675 | def __java_final (self, home):
1676 | path = [ home ]
1677 | subdir = []
1678 | try:
1679 | for sub in os.listdir(home):
1680 | newpath = os.path.join(home, sub)
1681 | if os.path.isdir(newpath):
1682 | import difflib
1683 | m = difflib.SequenceMatcher(None, sys.platform, sub)
1684 | subdir.append((m.ratio(), sub))
1685 | except:
1686 | pass
1687 | subdir.sort()
1688 | if subdir:
1689 | path.append(os.path.join(home, subdir[-1][1]))
1690 | return ' '.join([ '-I%s'%self.pathtext(n) for n in path ])
1691 |
1692 | # 取得 java配置
1693 | def java_home (self):
1694 | jdk = self._getitem('default', 'java', None)
1695 | if jdk:
1696 | jdk = os.path.abspath(jdk)
1697 | if os.path.exists(os.path.join(jdk, 'include/jni.h')):
1698 | return jdk
1699 | jdk = os.environ.get('JAVA_HOME', None)
1700 | if jdk:
1701 | jdk = os.path.abspath(jdk)
1702 | if os.path.exists(jdk):
1703 | return jdk
1704 | spliter = self.unix and ':' or ';'
1705 | PATH = os.environ.get('PATH', '')
1706 | for path in PATH.split(spliter):
1707 | path = path.strip('\r\n\t ')
1708 | if not os.path.exists(path):
1709 | continue
1710 | fn = os.path.join(path, 'javac')
1711 | if not self.unix: fn += '.exe'
1712 | if not os.path.exists(fn):
1713 | continue
1714 | fn = self.readlink(fn)
1715 | if not os.path.exists(fn):
1716 | continue
1717 | pp = os.path.abspath(os.path.join(os.path.dirname(fn), '..'))
1718 | pp = os.path.join(pp, 'include')
1719 | if not os.path.exists(pp):
1720 | continue
1721 | jni = os.path.join(pp, 'jni.h')
1722 | if os.path.exists(jni):
1723 | pp = os.path.join(pp, '../')
1724 | return os.path.abspath(pp)
1725 | if self.unix:
1726 | for i in range(20, 4, -1):
1727 | n = '/usr/local/openjdk%d'%i
1728 | if os.path.exists(os.path.join(n, 'include/jni.h')):
1729 | return os.path.abspath(n)
1730 | n = '/usr/jdk/instances/jdk1.%d.0'%i
1731 | if os.path.exists(os.path.join(n, 'include/jni.h')):
1732 | return os.path.abspath(n)
1733 | return ''
1734 |
1735 | # 取得 java配置
1736 | def java_config (self):
1737 | cflags = self._getitem('default', 'java_cflags', None)
1738 | if cflags:
1739 | return cflags.strip('\r\n\t ')
1740 | jdk = self.java_home()
1741 | if not jdk:
1742 | return ''
1743 | return self.__java_final(os.path.join(jdk, 'include'))
1744 |
1745 | # 执行 java命令: cmd 为 java, javac, jar等
1746 | def java_call (self, cmd, args = [], capture = False):
1747 | if self.__jdk_home is None:
1748 | self.__jdk_home = self.java_home()
1749 | if not self.__jdk_home:
1750 | sys.stderr.write('can not find java in $JAVA_HOME or $PATH\n')
1751 | sys.stderr.flush()
1752 | sys.exit(1)
1753 | return None
1754 | if not self.unix:
1755 | ext = os.path.splitext(cmd)[-1].lower()
1756 | if not ext:
1757 | cmd += '.exe'
1758 | cc = os.path.join(self.__jdk_home, 'bin/%s'%cmd)
1759 | if not os.path.exists(cc):
1760 | sys.stderr.write('can not find %s in %s\n'%(cmd, self.__jdk_home))
1761 | sys.stderr.flush()
1762 | sys.exit(1)
1763 | return None
1764 | cmd = cc
1765 | if not self.unix:
1766 | cmd = self.pathshort(cmd)
1767 | cmds = [ cmd ]
1768 | for n in args:
1769 | cmds.append(n)
1770 | return execute(cmds, False, capture)
1771 |
1772 |
1773 | #----------------------------------------------------------------------
1774 | # coremake: 核心工程编译,提供 Compile/Link/Build
1775 | #----------------------------------------------------------------------
1776 | class coremake(object):
1777 |
1778 | # 构造函数
1779 | def __init__ (self, ininame = ''):
1780 | self.ininame = ininame
1781 | self.config = configure(self.ininame)
1782 | self.unix = self.config.unix
1783 | self.inited = 0
1784 | self.extnames = self.config.extnames
1785 | self.envos = {}
1786 | for k, v in os.environ.items():
1787 | self.envos[k] = v
1788 | self.reset()
1789 |
1790 | # 复位配置
1791 | def reset (self):
1792 | self.config.reset()
1793 | self._out = '' # 最终输出的文件,比如abc.exe
1794 | self._int = '' # 中间文件的目录
1795 | self._main = '' # 主文件(工程文件)
1796 | self._mode = 'exe' # exe win dll lib
1797 | self._src = [] # 源代码
1798 | self._obj = [] # 目标文件
1799 | self._opt = []
1800 | self._export = {} # DLL导出配置
1801 | self._environ = {} # 环境变量
1802 | self.inited = 0
1803 |
1804 | # 初始化:设置工程名字,类型,以及中间文件的目录
1805 | def init (self, main, out = 'a.out', mode = 'exe', intermediate = '', profile = None):
1806 | if mode not in ('exe', 'win', 'dll', 'lib'):
1807 | raise Exception("mode must in ('exe', 'win', 'dll', 'lib')")
1808 | self.reset()
1809 | self.config.init()
1810 | self.config.loadcfg(profile)
1811 | self._main = os.path.abspath(main)
1812 | self._mode = mode
1813 | self._out = os.path.abspath(out)
1814 | self._int = intermediate
1815 | self._out = self.outname(self._out, mode)
1816 |
1817 | # 取得源文件对应的目标文件:给定源文件名和中间文件目录名
1818 | def objname (self, srcname, intermediate = ''):
1819 | part = os.path.splitext(srcname)
1820 | ext = part[1].lower()
1821 | if ext in self.extnames:
1822 | if intermediate:
1823 | name = os.path.join(intermediate, os.path.split(part[0])[-1])
1824 | name = os.path.abspath(name + '.o')
1825 | else:
1826 | name = os.path.abspath(part[0] + '.o')
1827 | return name
1828 | if ext not in ('.o', '.obj'):
1829 | raise Exception('unknow ext-type of %s\n'%srcname)
1830 | return srcname
1831 |
1832 | # 取得输出文件的文件名
1833 | def outname (self, output, mode = 'exe'):
1834 | if mode not in ('exe', 'win', 'dll', 'lib'):
1835 | raise Exception("mode must in ('exe', 'win', 'dll', 'lib')")
1836 | part = os.path.splitext(os.path.abspath(output))
1837 | output = part[0]
1838 | if mode == 'exe':
1839 | if self.unix == 0 and part[1] == '':
1840 | output += '.exe'
1841 | elif part[1]:
1842 | output += part[1]
1843 | elif mode == 'win':
1844 | if self.unix == 0 and part[1] == '':
1845 | output += '.exe'
1846 | elif part[1]:
1847 | output += part[1]
1848 | elif mode == 'dll':
1849 | if not part[1]:
1850 | if not self.unix: output += '.dll'
1851 | else: output += '.so'
1852 | else:
1853 | output += part[1]
1854 | elif mode == 'lib':
1855 | if not part[1]: output += '.a'
1856 | else: output += part[1]
1857 | return output
1858 |
1859 | # 根据源文件列表取得目标文件列表
1860 | def scan (self, sources, intermediate = ''):
1861 | src2obj = {}
1862 | obj2src = {}
1863 | for src in sources:
1864 | obj = self.objname(src, intermediate)
1865 | if obj in obj2src:
1866 | p1, p2 = os.path.splitext(obj)
1867 | index = 1
1868 | while True:
1869 | name = '%s%d%s'%(p1, index, p2)
1870 | if name not in obj2src:
1871 | obj = name
1872 | break
1873 | index += 1
1874 | src2obj[src] = obj
1875 | obj2src[obj] = src
1876 | obj2src = None
1877 | return src2obj
1878 |
1879 | # 添加源文件和目标文件
1880 | def push (self, srcname, objname, options):
1881 | self._src.append(os.path.abspath(srcname))
1882 | self._obj.append(os.path.abspath(objname))
1883 | self._opt.append(options)
1884 |
1885 | # 创建目录
1886 | def mkdir (self, path):
1887 | path = os.path.abspath(path)
1888 | if os.path.exists(path):
1889 | return 0
1890 | name = ''
1891 | part = os.path.abspath(path).replace('\\', '/').split('/')
1892 | if self.unix:
1893 | name = '/'
1894 | if (not self.unix) and (path[1:2] == ':'):
1895 | part[0] += '/'
1896 | for n in part:
1897 | name = os.path.abspath(os.path.join(name, n))
1898 | if not os.path.exists(name):
1899 | os.mkdir(name)
1900 | return 0
1901 |
1902 | # 删除目录
1903 | def remove (self, path):
1904 | try: os.remove(path)
1905 | except: pass
1906 | if os.path.exists(path):
1907 | sys.stderr.write('error: cannot remove \'%s\'\n'%path)
1908 | sys.stderr.flush()
1909 | sys.exit(0)
1910 | return 0
1911 |
1912 | # DLL配置
1913 | def dllwrap (self, name):
1914 | if sys.platform[:3] != 'win':
1915 | return -1
1916 | if self._mode != 'dll':
1917 | return -2
1918 | name = name.lower()
1919 | main = os.path.splitext(os.path.abspath(self._out))[0]
1920 | main = os.path.split(main)[-1]
1921 | main = os.path.abspath(os.path.join(self._int, main))
1922 | if name == 'def':
1923 | self._export['def'] = main + '.def'
1924 | elif name == 'lib':
1925 | self._export['lib'] = main + '.a'
1926 | elif name in ('hidden', 'hide', 'none'):
1927 | self._export['hide'] = 1
1928 | elif name in ('msvc', 'MSVC'):
1929 | self._export['def'] = main + '.def'
1930 | self._export['msvc'] = main + '.lib'
1931 | self._export['msvc64'] = 0
1932 | elif name in ('msvc64', 'MSVC64'):
1933 | self._export['def'] = main + '.def'
1934 | self._export['msvc'] = main + '.lib'
1935 | self._export['msvc64'] = 1
1936 | return 0
1937 |
1938 | # DLL export的参数
1939 | def _dllparam (self):
1940 | defname = self._export.get('def', '')
1941 | libname = self._export.get('lib', '')
1942 | msvclib = self._export.get('msvc', '') # noqa: F841
1943 | hidden = self._export.get('hide', 0)
1944 | if (not defname) and (not libname):
1945 | return ''
1946 | param = ''
1947 | if not hidden: param += '--export-all '
1948 | if defname:
1949 | param += '--output-def %s '%self.config.pathrel(defname)
1950 | if libname:
1951 | param += '--implib %s '%self.config.pathrel(libname)
1952 | return param
1953 |
1954 | # DLL 编译完成后的事情
1955 | def _dllpost (self):
1956 | defname = self._export.get('def', '')
1957 | libname = self._export.get('lib', '') # noqa: F841
1958 | msvclib = self._export.get('msvc', '')
1959 | dllname = self._out # noqa: F841
1960 | if not msvclib:
1961 | return 0
1962 | if not os.path.exists(defname):
1963 | return -1
1964 | machine = '/machine:i386'
1965 | msvc64 = self._export.get('msvc64', 0)
1966 | if msvc64:
1967 | machine = '/machine:x64'
1968 | defname = self.config.pathtext(self.config.pathrel(defname))
1969 | msvclib = self.config.pathtext(self.config.pathrel(msvclib))
1970 | parameters = '-nologo ' + machine + ' /def:' + defname
1971 | parameters += ' /out:' + msvclib
1972 | self.config.cmdtool('msvc', 'LIB.EXE ' + parameters, False)
1973 | return 0
1974 |
1975 | # 单核编译:skipexist(是否需要跳过已有的obj文件)
1976 | def _compile_single (self, skipexist, printmode, printcmd):
1977 | retval = 0
1978 | for i in range(len(self._src)):
1979 | srcname = self._src[i]
1980 | objname = self._obj[i]
1981 | options = self._opt[i]
1982 | if srcname == objname:
1983 | continue
1984 | if skipexist and os.path.exists(objname):
1985 | continue
1986 | try: os.remove(os.path.abspath(objname))
1987 | except: pass
1988 | if printmode & PRINT_MODE_SHOWFILE:
1989 | name = self.config.pathrel(srcname)
1990 | if name[:1] == '"':
1991 | name = name[1:-1]
1992 | if CFG['abspath']:
1993 | name = os.path.abspath(srcname)
1994 | print(name)
1995 | self.config.compile(srcname, objname, options, printcmd)
1996 | if not os.path.exists(objname):
1997 | retval = -1
1998 | break
1999 | return retval
2000 |
2001 | # 多核编译:skipexist(是否需要跳过已有的obj文件)
2002 | def _compile_threading (self, skipexist, printmode, printcmd, cpus):
2003 | # 估算编译时间,文件越大假设时间越长,放在最前面
2004 | ctasks = [ (os.path.getsize(s), s, o, t) for s, o, t in zip(self._src, self._obj, self._opt) ]
2005 | ctasks.sort()
2006 | import threading
2007 | self._task_lock = threading.Lock()
2008 | self._task_retval = 0
2009 | self._task_finish = False
2010 | self._task_queue = ctasks
2011 | self._task_thread = []
2012 | self._task_error = ''
2013 | for n in range(cpus):
2014 | parameters = (skipexist, printmode, printcmd, cpus - 1 - n)
2015 | th = threading.Thread(target = self._compile_working_thread, args = parameters)
2016 | self._task_thread.append(th)
2017 | for th in self._task_thread:
2018 | th.start()
2019 | for th in self._task_thread:
2020 | th.join()
2021 | self._task_thread = None
2022 | self._task_lock = None
2023 | self._task_queue = None
2024 | for objname in self._obj:
2025 | if not os.path.exists(objname):
2026 | self._task_retval = -1
2027 | break
2028 | return self._task_retval
2029 |
2030 | # 具体编译线程
2031 | def _compile_working_thread (self, skipexist, printmode, printcmd, id):
2032 | mutex = self._task_lock
2033 | while True:
2034 | weight, srcname, objname = 0, '', ''
2035 | mutex.acquire()
2036 | if self._task_finish:
2037 | mutex.release()
2038 | break
2039 | if not self._task_queue:
2040 | mutex.release()
2041 | break
2042 | weight, srcname, objname, options = self._task_queue.pop()
2043 | mutex.release()
2044 | if srcname == objname:
2045 | continue
2046 | if skipexist and os.path.exists(objname):
2047 | continue
2048 | try: os.remove(os.path.abspath(objname))
2049 | except: pass
2050 | timeslap = time.time()
2051 | output = self.config.compile(srcname, objname, options, printcmd, True)
2052 | timeslap = time.time() - timeslap
2053 | result = True
2054 | if not os.path.exists(objname):
2055 | mutex.acquire()
2056 | self._task_retval = -1
2057 | self._task_finish = True
2058 | mutex.release()
2059 | result = False # noqa: F841
2060 | mutex.acquire()
2061 | if printmode & PRINT_MODE_SHOWFILE:
2062 | name = self.config.pathrel(srcname)
2063 | if name[:1] == '"':
2064 | name = name[1:-1]
2065 | if CFG['abspath']:
2066 | name = os.path.abspath(srcname)
2067 | sys.stdout.write(name + '\n')
2068 | if sys.platform[:3] == 'win':
2069 | lines = [ x.rstrip('\r\n') for x in output.split('\n') ]
2070 | output = '\n'.join(lines)
2071 | sys.stdout.write(output)
2072 | sys.stdout.flush()
2073 | mutex.release()
2074 | time.sleep(0.01)
2075 | return 0
2076 |
2077 | # 编译:skipexist(是否需要跳过已有的obj文件)
2078 | def compile (self, skipexist = False, printmode = 0, cpus = 0):
2079 | self.config.check()
2080 | self.mkdir(os.path.abspath(self._int))
2081 | printcmd = bool(printmode & PRINT_MODE_SHOWCMD)
2082 | if printmode & PRINT_MODE_SHOWSTAT:
2083 | print('compiling ...')
2084 | t = time.time()
2085 | if cpus <= 1:
2086 | retval = self._compile_single(skipexist, printmode, printcmd)
2087 | else:
2088 | retval = self._compile_threading(skipexist, printmode, printcmd, cpus)
2089 | t = time.time() - t
2090 | #print('time', t)
2091 | return retval
2092 |
2093 | # 连接:(是否跳过已有的文件)
2094 | def link (self, skipexist = False, printmode = 0):
2095 | self.config.check()
2096 | retval = 0 # noqa: F841
2097 | printcmd = bool(printmode & PRINT_MODE_SHOWCMD)
2098 | if printmode & PRINT_MODE_SHOWSTAT:
2099 | print('linking ...')
2100 | output = self._out
2101 | if skipexist and os.path.exists(output):
2102 | return output
2103 | self.remove(output)
2104 | self.mkdir(os.path.split(output)[0])
2105 | if self._mode == 'exe':
2106 | self.config.makeexe(output, self._obj, '', printcmd)
2107 | elif self._mode == 'win':
2108 | param = '-mwindows'
2109 | self.config.makeexe(output, self._obj, param, printcmd)
2110 | elif self._mode == 'dll':
2111 | param = self._dllparam()
2112 | self.config.makedll(output, self._obj, param, printcmd)
2113 | if param and os.path.exists(output):
2114 | self._dllpost()
2115 | elif self._mode == 'lib':
2116 | self.config.makelib(output, self._obj, printcmd)
2117 | if not os.path.exists(output):
2118 | return ''
2119 | return output
2120 |
2121 | # 执行编译事件
2122 | def event (self, scripts):
2123 | if not scripts:
2124 | return False
2125 | # 保存环境
2126 | envsave = {}
2127 | for k, v in os.environ.items():
2128 | envsave[k] = v
2129 | # 初始化环境
2130 | environ = {}
2131 | for k, v in self._environ.items():
2132 | environ[k] = v
2133 | environ['EMAKE_SCRIPT'] = os.path.abspath(__file__)
2134 | environ['EMAKE_SCRIPT_PATH'] = os.path.dirname(os.path.abspath(__file__))
2135 | environ['EMAKE_TOOLCHAIN'] = self.config.dirhome
2136 | environ['EMAKE_OUT'] = self._out
2137 | environ['EMAKE_INT'] = self._int
2138 | environ['EMAKE_MAIN'] = self._main
2139 | environ['EMAKE_PATH'] = os.path.dirname(self._main)
2140 | environ['EMAKE_MODE'] = self._mode
2141 | environ['EMAKE_HOME'] = os.path.dirname(self._main)
2142 | environ['EMAKE_MAIN_NOEXT'] = os.path.splitext(self._main)[0]
2143 | environ['EMAKE_MAIN_EXT'] = os.path.splitext(self._main)[1]
2144 | environ['EMAKE_MAIN_PATH'] = os.path.dirname(self._main)
2145 | environ['EMAKE_OUT_NOEXT'] = os.path.splitext(self._out)[0]
2146 | environ['EMAKE_OUT_EXT'] = os.path.splitext(self._out)[1]
2147 | environ['EMAKE_OUT_PATH'] = os.path.dirname(self._out)
2148 | environ['EMAKE_TARGET'] = self.config.target
2149 | for name in ('gcc', 'ar', 'ld', 'as', 'nasm', 'yasm', 'dllwrap'):
2150 | environ['EMAKE_BIN_' + name.upper()] = self.config.getname(name)
2151 | inipath = ''
2152 | if INIPATH:
2153 | if os.path.exists(INIPATH):
2154 | inipath = INIPATH
2155 | if not inipath:
2156 | inipath = self.config.iniload
2157 | environ['EMAKE_CONFIG'] = inipath
2158 | for k, v in environ.items(): # 展开宏
2159 | environ[k] = self.config._expand(environ, envsave, k)
2160 | for k, v in environ.items():
2161 | os.environ[k] = v
2162 | # 执行应用
2163 | workdir = os.path.dirname(self._main)
2164 | savecwd = os.getcwd()
2165 | for script in scripts:
2166 | if savecwd != workdir:
2167 | os.chdir(workdir)
2168 | os.system(script)
2169 | os.chdir(savecwd)
2170 | # 恢复环境
2171 | for k, v in envsave.items():
2172 | if os.environ.get(k) != v:
2173 | os.environ[k] = v
2174 | for k in list(os.environ.keys()):
2175 | if k not in envsave:
2176 | del os.environ[k]
2177 | return True
2178 |
2179 | # 编译与连接
2180 | def build (self, skipexist = False, printmode = 0):
2181 | if self.compile(skipexist, printmode) != 0:
2182 | return -1
2183 | output = self.link(skipexist, printmode)
2184 | if output == '':
2185 | return -2
2186 | return output
2187 |
2188 |
2189 |
2190 | #----------------------------------------------------------------------
2191 | # iparser: 工程分析器,分析各种配置信息
2192 | #----------------------------------------------------------------------
2193 | class iparser (object):
2194 |
2195 | # 构造函数
2196 | def __init__ (self, ininame = ''):
2197 | self.preprocessor = preprocessor()
2198 | self.coremake = coremake(ininame)
2199 | self.config = self.coremake.config
2200 | self.extnames = self.config.extnames
2201 | self.reset()
2202 |
2203 | # 配置复位
2204 | def reset (self):
2205 | self.src = []
2206 | self.inc = []
2207 | self.lib = []
2208 | self.imp = []
2209 | self.exp = []
2210 | self.link = []
2211 | self.flag = []
2212 | self.flnk = []
2213 | self.wlnk = []
2214 | self.cond = []
2215 | self.pkg = []
2216 | self.pkgflag = []
2217 | self.environ = {}
2218 | self.events = {}
2219 | self.mode = 'exe'
2220 | self.define = {}
2221 | self.name = ''
2222 | self.home = ''
2223 | self.info = 3
2224 | self.out = ''
2225 | self.int = ''
2226 | self.makefile = ''
2227 | self.pending_check = []
2228 | self.incdict = {}
2229 | self.libdict = {}
2230 | self.srcdict = {}
2231 | self.chkdict = {}
2232 | self.optdict = {}
2233 | self.impdict = {}
2234 | self.expdict = {}
2235 | self.pkgdict = {}
2236 | self.linkdict = {}
2237 | self.flagdict = {}
2238 | self.flnkdict = {}
2239 | self.wlnkdict = {}
2240 | self.conddict = {}
2241 | self.profile = ''
2242 | self.makefile = ''
2243 |
2244 | # 取得文件的目标文件名称
2245 | def __getitem__ (self, key):
2246 | return self.srcdict[key]
2247 |
2248 | # 取得模块个数
2249 | def __len__ (self):
2250 | return len(self.srcdict)
2251 |
2252 | # 检测是否包含模块
2253 | def __contains__ (self, key):
2254 | return (key in self.srcdict)
2255 |
2256 | # 取得迭代器
2257 | def __iter__ (self):
2258 | return self.src.__iter__()
2259 |
2260 | # 添加代码
2261 | def push_src (self, filename, options):
2262 | filename = os.path.abspath(filename)
2263 | realname = os.path.normcase(filename)
2264 | if filename in self.srcdict:
2265 | return -1
2266 | if realname in self.chkdict:
2267 | return -1
2268 | self.srcdict[filename] = ''
2269 | self.chkdict[realname] = filename
2270 | self.optdict[filename] = options
2271 | self.src.append(filename)
2272 | return 0
2273 |
2274 | # 添加链接
2275 | def push_link (self, linkname):
2276 | if linkname in self.linkdict:
2277 | return -1
2278 | self.linkdict[linkname] = len(self.link)
2279 | self.link.append(linkname)
2280 | return 0
2281 |
2282 | # 添加头路径
2283 | def push_inc (self, inc):
2284 | if inc in self.incdict:
2285 | return -1
2286 | self.incdict[inc] = len(self.inc)
2287 | self.inc.append(inc)
2288 | return 0
2289 |
2290 | # 添加库路径
2291 | def push_lib (self, lib):
2292 | if lib in self.libdict:
2293 | return -1
2294 | self.libdict[lib] = len(self.lib)
2295 | self.lib.append(lib)
2296 | return 0
2297 |
2298 | # 添加参数
2299 | def push_flag (self, flag):
2300 | if flag in self.flagdict:
2301 | return -1
2302 | self.flagdict[flag] = len(self.flag)
2303 | self.flag.append(flag)
2304 | return 0
2305 |
2306 | # 添加宏定义
2307 | def push_define (self, define, value = 1):
2308 | self.define[define] = value
2309 | return 0
2310 |
2311 | # 添加连接参数
2312 | def push_flnk (self, flnk):
2313 | if flnk in self.flnkdict:
2314 | return -1
2315 | self.flnkdict[flnk] = len(self.flnk)
2316 | self.flnk.append(flnk)
2317 | return 0
2318 |
2319 | # 添加连接传递
2320 | def push_wlnk (self, wlnk):
2321 | if wlnk in self.wlnkdict:
2322 | return -1
2323 | self.wlnkdict[wlnk] = len(self.wlnk)
2324 | self.wlnk.append(wlnk)
2325 | return 0
2326 |
2327 | # 添加条件编译
2328 | def push_cond (self, flag, condition):
2329 | key = (flag, condition)
2330 | if key in self.conddict:
2331 | return -1
2332 | self.conddict[key] = len(self.cond)
2333 | self.cond.append(key)
2334 | return 0
2335 |
2336 | # new import
2337 | def push_imp (self, name, fname = '', lineno = -1):
2338 | if name in self.impdict:
2339 | return -1
2340 | self.impdict[name] = len(self.imp)
2341 | self.imp.append((name, fname, lineno))
2342 | return 0
2343 |
2344 | # new package
2345 | def push_pkg (self, name, fname = '', lineno = -1):
2346 | if name in self.pkgdict:
2347 | return -1
2348 | self.pkgdict[name] = len(self.pkg)
2349 | self.pkg.append((name, fname, lineno))
2350 | return 0
2351 |
2352 | # new export
2353 | def push_exp (self, name, fname = '', lineno = -1):
2354 | if name in self.expdict:
2355 | return -1
2356 | self.expdict[name] = len(self.exp)
2357 | self.exp.append((name, fname, lineno))
2358 | return 0
2359 |
2360 | # new pkgflag
2361 | def push_pkgflag (self, name, fname = '', lineno = -1):
2362 | self.pkgflag.append((name, fname, lineno))
2363 |
2364 | # 添加环境变量
2365 | def push_environ (self, name, value):
2366 | self.environ[name] = value
2367 |
2368 | # 添加编译事件
2369 | def push_event (self, name, value):
2370 | if name not in self.events:
2371 | self.events[name] = []
2372 | self.events[name].append(value)
2373 |
2374 | # 分析开始
2375 | def parse (self, makefile, profile = None):
2376 | self.reset()
2377 | self.config.init()
2378 | if profile:
2379 | self.profile = profile
2380 | makefile = os.path.abspath(makefile)
2381 | self.makefile = makefile
2382 | part = os.path.split(makefile)
2383 | self.home = part[0]
2384 | self.name = os.path.splitext(part[1])[0]
2385 | if not os.path.exists(makefile):
2386 | sys.stderr.write('error: %s cannot be open\n'%(makefile))
2387 | sys.stderr.flush()
2388 | return -1
2389 | cfg = self.config.config.get('default', {})
2390 | for name in ('prebuild', 'prelink', 'postbuild'):
2391 | body = cfg.get(name, '').strip('\r\n\t ').split('&&')
2392 | for script in body:
2393 | script = script.strip('\r\n\t ')
2394 | self.push_event(name, script)
2395 | extname = os.path.splitext(makefile)[1].lower()
2396 | if extname in ('.mak', '.em', '.emk', '.pyx', '.py'):
2397 | if self.scan_makefile() != 0:
2398 | return -3
2399 | elif extname in self.extnames:
2400 | if self.scan_mainfile() != 0:
2401 | return -4
2402 | else:
2403 | sys.stderr.write('error: unknow file type of "%s"\n'%makefile)
2404 | sys.stderr.flush()
2405 | return -5
2406 | if not self.out:
2407 | self.out = os.path.splitext(makefile)[0]
2408 | self.out = self.coremake.outname(self.out, self.mode)
2409 | self._update_obj_names()
2410 | return 0
2411 |
2412 | # 取得相对路径
2413 | def pathrel (self, name, current = ''):
2414 | if not current:
2415 | current = os.getcwd()
2416 | current = current.replace('\\', '/')
2417 | if len(current) > 0:
2418 | if current[-1] != '/':
2419 | current += '/'
2420 | name = self.path(name).replace('\\', '/')
2421 | size = len(current)
2422 | if name[:size] == current:
2423 | name = name[size:]
2424 | return name
2425 |
2426 | # 配置路径
2427 | def pathconf (self, path):
2428 | path = path.strip(' \r\n\t')
2429 | if path[:1] == '\'' and path[-1:] == '\'': path = path[1:-1]
2430 | if path[:1] == '\"' and path[-1:] == '\"': path = path[1:-1]
2431 | return path.strip(' \r\n\t')
2432 |
2433 | # 扫描代码中 关键注释的工程信息
2434 | def _scan_memo (self, filename, prefix = '!'):
2435 | command = []
2436 | content = posix.load_file_text(filename)
2437 | srctext = self.preprocessor.preprocess(content)
2438 | srcline = [ 0 for i in range(len(srctext)) ]
2439 | length = len(srctext)
2440 | lineno = 1
2441 | for i in range(len(srctext)):
2442 | srcline[i] = lineno
2443 | if srctext[i] == '\n':
2444 | lineno += 1
2445 | start = 0
2446 | endup = 0
2447 | while (start >= 0) and (start < length):
2448 | start = endup
2449 | endup = srctext.find('`', start)
2450 | if endup < 0:
2451 | break
2452 | start = endup
2453 | head = content[start:start + 2]
2454 | body = ''
2455 | if head == '//':
2456 | endup = srctext.find('\n', start)
2457 | if endup < 0: endup = length
2458 | body = content[start + 2:endup]
2459 | endup += 1
2460 | elif head == '/*':
2461 | endup = content.find('*/', start)
2462 | if endup < 0: endup = length
2463 | body = content[start + 2:endup]
2464 | endup += 2
2465 | else:
2466 | Exception ('error comment')
2467 | if body[:len(prefix)] != prefix:
2468 | continue
2469 | pos = start + 2 + len(prefix)
2470 | body = body[len(prefix):]
2471 | if pos >= length: pos = length - 1
2472 | lineno = srcline[pos]
2473 | for n in body.split('\n'):
2474 | command.append((lineno, n.strip('\r\n').strip(' \t')))
2475 | lineno += 1
2476 | return command
2477 |
2478 | # 扫描主文件
2479 | def scan_mainfile (self):
2480 | command = self._scan_memo(self.makefile)
2481 | savedir = os.getcwd()
2482 | os.chdir(os.path.split(self.makefile)[0])
2483 | retval = 0
2484 | for lineno, text in command:
2485 | if self._process(self.makefile, lineno, text) != 0:
2486 | retval = -1
2487 | break
2488 | self._process_int()
2489 | os.chdir(savedir)
2490 | self.push_src(self.makefile, '')
2491 | return retval
2492 |
2493 | # 扫描工程文件
2494 | def scan_makefile (self):
2495 | savedir = os.getcwd()
2496 | os.chdir(os.path.split(self.makefile)[0])
2497 | ext = os.path.splitext(self.makefile)[1].lower()
2498 | lineno = 1
2499 | retval = 0
2500 | for text in posix.load_file_text(self.makefile).split('\n'):
2501 | if ext in ('.pyx', '.py'):
2502 | text = text.strip('\r\n\t ')
2503 | if text[:3] != '##!':
2504 | continue
2505 | text = text[3:]
2506 | if self._process(self.makefile, lineno, text) != 0:
2507 | retval = -1
2508 | break
2509 | lineno += 1
2510 | self._process_int()
2511 | os.chdir(savedir)
2512 | return retval
2513 |
2514 | # 输出错误
2515 | def error (self, text, fname = '', line = -1):
2516 | message = ''
2517 | if fname and line > 0:
2518 | message = '%s:%d: '%(fname, line)
2519 | sys.stderr.write(message + text + '\n')
2520 | sys.stderr.flush()
2521 | return 0
2522 |
2523 | # 编译前检测错误:比如文件不存在
2524 | def check_error (self):
2525 | hr = 0
2526 | for absname, srcname, mainfile, lineno in self.pending_check:
2527 | if not os.path.exists(absname):
2528 | self.error('error: %s: No such file'%srcname,
2529 | mainfile, lineno)
2530 | hr = 1
2531 | return hr
2532 |
2533 | # 处理 --int=xxx
2534 | def _process_int (self):
2535 | if self.int != '':
2536 | return 0
2537 | if 'int' not in CFG:
2538 | return 0
2539 | t = CFG['int'].strip('\r\n\t ')
2540 | if not t:
2541 | return 0
2542 | text = 'int: ' + CFG['int']
2543 | self._process('', 0, text)
2544 | return 0
2545 |
2546 | # 处理源文件
2547 | def _process_src (self, textline, fname = '', lineno = -1):
2548 | ext1 = ('.c', '.cpp', '.cc', '.cxx', '.asm')
2549 | ext2 = ('.s', '.o', '.obj', '.m', '.mm')
2550 | pos = textline.find(':')
2551 | body, options = textline, ''
2552 | pos = textline.rfind(':')
2553 | if pos >= 0:
2554 | split = (sys.platform[:3] != 'win') and True or False
2555 | if sys.platform[:3] == 'win':
2556 | if textline[pos:pos + 2] not in (':/', ':\\'):
2557 | split = True
2558 | if split:
2559 | body = textline[:pos].strip('\r\n\t ')
2560 | options = textline[pos + 1:].strip('\r\n\t ')
2561 | for name in body.replace(';', ',').split(','):
2562 | srcname = self.pathconf(name)
2563 | if not srcname:
2564 | continue
2565 | if ('*' not in srcname) and ('?' not in srcname):
2566 | names = [ srcname ]
2567 | else:
2568 | import glob
2569 | names = glob.glob(srcname)
2570 | for srcname in names:
2571 | absname = os.path.abspath(srcname)
2572 | self.pending_check.append((absname, srcname, fname, lineno))
2573 | extname = os.path.splitext(absname)[1].lower()
2574 | if (extname not in ext1) and (extname not in ext2):
2575 | self.error('error: %s: Unknow file type'%absname,
2576 | fname, lineno)
2577 | return -2
2578 | self.push_src(absname, options)
2579 | return 0
2580 |
2581 | # 处理:分析信息
2582 | def _process (self, fname, lineno, text):
2583 | text = text.strip(' \t\r\n')
2584 | if not text: # 空行
2585 | return 0
2586 | if text[:1] in (';', '#'): # 跳过注释
2587 | return 0
2588 | # print('>', text)
2589 | pos = text.find(':')
2590 | if pos < 0:
2591 | self.error('unknow emake command', fname, lineno)
2592 | return -1
2593 | command, body = text[:pos].lower(), text[pos + 1:]
2594 | pos = command.find('/')
2595 | if pos >= 0:
2596 | condition, command = command[:pos].lower(), command[pos + 1:]
2597 | match = False
2598 | for cond in condition.replace(';', ',').split(','):
2599 | cond = cond.strip('\r\n\t ')
2600 | if not cond: continue
2601 | if cond in self.config.name:
2602 | match = True
2603 | break
2604 | if not match:
2605 | #print('"%s" not in %s'%(condition, self.config.name))
2606 | return 0
2607 | pos = command.find('@')
2608 | if pos >= 0:
2609 | command, profile = command[:pos].lower(), command[pos + 1:]
2610 | command = command.strip()
2611 | profile = profile.strip()
2612 | if profile and profile != self.profile:
2613 | return 0
2614 | environ = {}
2615 | environ['target'] = self.config.target
2616 | environ['int'] = self.int
2617 | environ['out'] = self.out
2618 | environ['mode'] = self.mode
2619 | environ['home'] = os.path.dirname(os.path.abspath(fname))
2620 | environ['bin'] = self.config.dirhome
2621 | environ['profile'] = self.profile and self.profile or 'none'
2622 | names = ['gcc', 'ar', 'ld', 'as', 'nasm', 'yasm']
2623 | names = names + ['dllwrap', 'pkgconfig']
2624 | for name in names:
2625 | if name in self.config.exename:
2626 | data = self.config.exename[name]
2627 | environ[name] = os.path.join(self.config.dirhome, data)
2628 | environ['cc'] = environ['gcc']
2629 | for name in environ:
2630 | key = '$(%s)'%name
2631 | val = environ[name]
2632 | if key in body:
2633 | body = body.replace(key, val)
2634 | if command in ('out', 'output'):
2635 | self.out = os.path.abspath(self.pathconf(body))
2636 | return 0
2637 | if command in ('int', 'intermediate'):
2638 | body = body.strip('\r\n\t ')
2639 | if body in ('', '$(auto)', '!', '?', '*', '+'):
2640 | dirname = self.config.target
2641 | if self.profile:
2642 | dirname += '-' + self.profile
2643 | self.int = os.path.abspath(os.path.join('objs', dirname))
2644 | else:
2645 | self.int = os.path.abspath(self.pathconf(body))
2646 | return 0
2647 | if command in ('src', 'source'):
2648 | retval = self._process_src(body, fname, lineno)
2649 | return retval
2650 | if command in ('mode', 'mod'):
2651 | body = body.lower().strip(' \r\n\t')
2652 | if body not in ('exe', 'win', 'lib', 'dll'):
2653 | self.error('error: %s: mode is not supported'%body,
2654 | fname, lineno)
2655 | return -1
2656 | self.mode = body
2657 | return 0
2658 | if command == 'link':
2659 | for name in body.replace(';', ',').split(','):
2660 | srcname = self.pathconf(name)
2661 | if not srcname:
2662 | continue
2663 | self.push_link(srcname)
2664 | return 0
2665 | if command in ('inc', 'lib'):
2666 | for name in body.replace(';', ',').split(','):
2667 | srcname = self.pathconf(name)
2668 | if not srcname:
2669 | continue
2670 | absname = os.path.abspath(srcname)
2671 | if not os.path.exists(absname):
2672 | self.error('error: %s: No such directory'%srcname,
2673 | fname, lineno)
2674 | return -1
2675 | if command == 'inc':
2676 | self.push_inc(absname)
2677 | elif command == 'lib':
2678 | self.push_lib(absname)
2679 | return 0
2680 | if command == 'flag':
2681 | for name in body.replace(';', ',').split(','):
2682 | srcname = self.pathconf(name)
2683 | if not srcname:
2684 | continue
2685 | if srcname[:2] in ('-o', '-I', '-B', '-L'):
2686 | self.error('error: %s: invalid option'%srcname,
2687 | fname, lineno)
2688 | self.push_flag(srcname)
2689 | return 0
2690 | if command in ('flnk', 'linkflag', 'flink'):
2691 | for name in body.replace(';', ',').split(','):
2692 | srcname = self.pathconf(name)
2693 | if not srcname:
2694 | continue
2695 | self.push_flnk(srcname)
2696 | return 0
2697 | if command in ('wlnk', 'wl', 'ld', 'wlink'):
2698 | for name in body.replace(';', ',').split(','):
2699 | srcname = self.pathconf(name)
2700 | if not srcname:
2701 | continue
2702 | self.push_wlnk(srcname)
2703 | return 0
2704 | for cond in ('cflag', 'cxxflag', 'sflag', 'mflag', 'mmflag'):
2705 | if command == cond or command.rstrip('s') == cond:
2706 | for name in body.replace(';', ',').split(','):
2707 | flag = self.pathconf(name)
2708 | if not flag:
2709 | continue
2710 | if flag[:2] in ('-o', '-I', '-B', '-L'):
2711 | self.error('error: %s: invalid option'%flag,
2712 | fname, lineno)
2713 | self.push_cond(flag, cond)
2714 | return 0
2715 | if command in ('arglink', 'al'):
2716 | self.push_flnk(body.strip('\r\n\t '))
2717 | return 0
2718 | if command in ('argcc', 'ac'):
2719 | self.push_flag(body.strip('\r\n\t '))
2720 | return 0
2721 | if command in ('pkg', 'package'):
2722 | for name in body.replace(';', ',').split(','):
2723 | name = self.pathconf(name)
2724 | if not name:
2725 | continue
2726 | self.push_pkg(name, fname, lineno)
2727 | return 0
2728 | if command in ('pkgflag', 'pcflag'):
2729 | for name in body.replace(';', ',').split(','):
2730 | name = self.pathconf(name)
2731 | if not name:
2732 | continue
2733 | self.push_pkgflag(name, fname, lineno)
2734 | return 0
2735 | if command == 'define':
2736 | for name in body.replace(';', ',').split(','):
2737 | srcname = self.pathconf(name).replace(' ', '_')
2738 | if not srcname:
2739 | continue
2740 | self.push_define(srcname)
2741 | return 0
2742 | if command == 'info':
2743 | body = body.strip(' \t\r\n').lower()
2744 | if body in ('0', 'false', 'off'):
2745 | self.info = 0
2746 | else:
2747 | try: info = int(body)
2748 | except: info = 3
2749 | self.info = info
2750 | return 0
2751 | if command in ('cexe', 'clib', 'cdll', 'cwin', 'exe', 'dll', 'win'):
2752 | if not self.int:
2753 | dirname = self.config.target
2754 | if self.profile:
2755 | dirname += '-' + self.profile
2756 | self.int = os.path.abspath(os.path.join('objs', dirname))
2757 | self.mode = command[-3:]
2758 | retval = self._process_src(body, fname, lineno)
2759 | return retval
2760 | if command in ('swf', 'swc', 'elf'):
2761 | self.mode = 'exe'
2762 | if not self.out:
2763 | self.out = os.path.splitext(fname)[0] + '.' + command
2764 | if not self.int:
2765 | self.int = os.path.abspath(os.path.join('objs', self.config.target))
2766 | body = body.strip('\r\n\t ')
2767 | if command == 'swf':
2768 | self.push_flnk('-emit-swf')
2769 | pos = body.find('x')
2770 | if pos >= 0:
2771 | try:
2772 | t1 = int(body[:pos])
2773 | t2 = int(body[pos + 1:])
2774 | except:
2775 | self.error('error: %s: bad size'%body, fname, lineno)
2776 | return -1
2777 | self.push_flnk('-swf-size=%dx%d'%(t1, t2))
2778 | elif body:
2779 | self.error('error: %s: bad size'%body, fname, lineno)
2780 | return -1
2781 | elif command == 'swc':
2782 | if not body:
2783 | self.error('error: namespace empty', fname, lineno)
2784 | return -1
2785 | self.push_flnk('-emit-swc=' + body.strip('\t\n\r '))
2786 | else:
2787 | return self._process_src(body, fname, lineno)
2788 | return 0
2789 | if command in ('imp', 'import'):
2790 | for name in body.replace(';', ',').split(','):
2791 | name = self.pathconf(name)
2792 | if not name:
2793 | continue
2794 | self.push_imp(name, fname, lineno)
2795 | return 0
2796 | if command in ('exp', 'export'):
2797 | self.dllexp = 'yes'
2798 | for name in body.replace(';', ',').split(','):
2799 | name = self.pathconf(name).lower()
2800 | if not name:
2801 | continue
2802 | self.push_exp(name, fname, lineno)
2803 | return 0
2804 | if command in ('pkg', 'package'):
2805 | for name in body.replace(';', ',').split(','):
2806 | name = self.pathconf(name)
2807 | if not name:
2808 | continue
2809 | self.push_pkg(name, fname, lineno)
2810 | return 0
2811 | if command == 'echo':
2812 | print(body)
2813 | return 0
2814 | if command == 'color':
2815 | self.console(int(body.strip('\r\n\t '), 0))
2816 | return 0
2817 | if command in ('prebuild', 'prelink', 'postbuild'):
2818 | self.push_event(command, body)
2819 | return 0
2820 | if command in ('preload'):
2821 | self.coremake._main = self.makefile
2822 | self.coremake.event([body])
2823 | return 0
2824 | if command == 'environ':
2825 | for name in body.replace(';', ',').split(','):
2826 | name = name.strip('\r\n\t ')
2827 | k, v = (name.split('=') + ['',''])[:2]
2828 | self.push_environ(k.strip('\r\n\t '), v.strip('\r\n\t '))
2829 | return 0
2830 | if command == 'use':
2831 | for name in body.replace(';', ',').split(','):
2832 | name = name.strip('\r\n\t ')
2833 | if name == 'python':
2834 | cflags, ldflags = self.config.python_config()
2835 | if cflags:
2836 | self.push_flag(cflags)
2837 | if ldflags:
2838 | self.push_flnk(ldflags)
2839 | elif name == 'java':
2840 | java = self.config.java_config()
2841 | if java:
2842 | self.push_flag(java)
2843 | else:
2844 | tt = 'error: %s: invalid name to use, only python or java'
2845 | self.error(tt%command, fname, lineno)
2846 | return -1
2847 | return 0
2848 | self.error('error: %s: invalid command'%command, fname, lineno)
2849 | return -1
2850 |
2851 | # 扫描并确定目标文件
2852 | def _update_obj_names (self):
2853 | src2obj = self.coremake.scan(self.src, self.int)
2854 | for fn in self.src:
2855 | obj = src2obj[fn]
2856 | self.srcdict[fn] = os.path.abspath(obj)
2857 | return 0
2858 |
2859 | # 取得某文件的编译参数
2860 | def compile_flag (self, srcname):
2861 | chkname = os.path.abspath(srcname)
2862 | chkname = os.path.normcase(chkname)
2863 | if chkname not in self.chkdict:
2864 | return None
2865 | srcname = self.chkdict[chkname]
2866 | options = self.optdict[srcname]
2867 | cond = self.config.cond_flags(srcname)
2868 | hr = self.config.param_compile.strip('\r\n\t ')
2869 | if options:
2870 | hr += ' ' + options
2871 | if cond:
2872 | hr += ' ' + (' '.join(cond))
2873 | return hr
2874 |
2875 | # 使用 gcc -MM 取得编译依赖
2876 | def mm (self, srcname):
2877 | chkname = os.path.abspath(srcname)
2878 | chkname = os.path.normcase(chkname)
2879 | options = ''
2880 | if chkname in self.chkdict:
2881 | srcname = self.chkdict[chkname]
2882 | options = self.optdict[srcname]
2883 | hr = self.config.mm(srcname, options)
2884 | return hr
2885 |
2886 | # 设置终端颜色
2887 | def console (self, color):
2888 | if not os.isatty(sys.stdout.fileno()):
2889 | return False
2890 | if sys.platform[:3] == 'win':
2891 | try: import ctypes
2892 | except: return 0
2893 | kernel32 = ctypes.windll.LoadLibrary('kernel32.dll')
2894 | GetStdHandle = kernel32.GetStdHandle
2895 | SetConsoleTextAttribute = kernel32.SetConsoleTextAttribute
2896 | GetStdHandle.argtypes = [ ctypes.c_uint32 ]
2897 | GetStdHandle.restype = ctypes.c_size_t
2898 | SetConsoleTextAttribute.argtypes = [ ctypes.c_size_t, ctypes.c_uint16 ]
2899 | SetConsoleTextAttribute.restype = ctypes.c_long
2900 | handle = GetStdHandle(0xfffffff5)
2901 | if color < 0: color = 7
2902 | result = 0
2903 | if (color & 1): result |= 4
2904 | if (color & 2): result |= 2
2905 | if (color & 4): result |= 1
2906 | if (color & 8): result |= 8
2907 | if (color & 16): result |= 64
2908 | if (color & 32): result |= 32
2909 | if (color & 64): result |= 16
2910 | if (color & 128): result |= 128
2911 | SetConsoleTextAttribute(handle, result)
2912 | else:
2913 | if color >= 0: # noqa
2914 | foreground = color & 7
2915 | background = (color >> 4) & 7
2916 | bold = color & 8
2917 | if background != 0:
2918 | sys.stdout.write(" \033[%s3%d;4%dm"%(bold and "01;" or "", foreground, background))
2919 | else:
2920 | sys.stdout.write(" \033[%s3%dm"%(bold and "01;" or "", foreground))
2921 | sys.stdout.flush()
2922 | else:
2923 | sys.stdout.write("\033[0m")
2924 | sys.stdout.flush()
2925 | return 0
2926 |
2927 |
2928 | #----------------------------------------------------------------------
2929 | # dependence: 工程编译,Compile/Link/Build
2930 | #----------------------------------------------------------------------
2931 | class dependence (object):
2932 |
2933 | def __init__ (self, parser = None):
2934 | self.parser = parser
2935 | self.preprocessor = preprocessor()
2936 | self.reset()
2937 |
2938 | def reset (self):
2939 | self._mtime = {}
2940 | self._dirty = {}
2941 | self._depinfo = {}
2942 | self._depname = ''
2943 | self._outchg = False
2944 |
2945 | def mtime (self, fname):
2946 | fname = os.path.abspath(fname)
2947 | if fname in self._mtime:
2948 | return self._mtime[fname]
2949 | try: mtime = os.path.getmtime(fname)
2950 | except: mtime = 0.0
2951 | mtime = float('%.6f'%mtime)
2952 | self._mtime[fname] = mtime
2953 | return mtime
2954 |
2955 | def _scan_include (self, srcname):
2956 | head, lost, src = self.preprocessor.dependence(srcname)
2957 | # print(srcname, head)
2958 | return head
2959 |
2960 | def _scan_src (self, srcname):
2961 | srcname = os.path.abspath(srcname)
2962 | if srcname not in self.parser:
2963 | return None
2964 | if not os.path.exists(srcname):
2965 | return None
2966 | objname = self.parser[srcname] # noqa: F841
2967 | head = self._scan_include(srcname)
2968 | filelist = [srcname] + head
2969 | dependence = []
2970 | for fn in filelist:
2971 | name = os.path.abspath(fn)
2972 | dependence.append((name, self.mtime(name)))
2973 | return dependence
2974 |
2975 | def _update_dep (self, srcname):
2976 | srcname = os.path.abspath(srcname)
2977 | if srcname not in self.parser:
2978 | return -1
2979 | retval = 0
2980 | debug = 0
2981 | if debug: print('\n'%srcname)
2982 | objname = self.parser[srcname]
2983 | srctime = self.mtime(srcname) # noqa: F841
2984 | objtime = self.mtime(objname)
2985 | update = False
2986 | info = self._depinfo.setdefault(srcname, {})
2987 | if len(info) == 0:
2988 | update = True
2989 | if not update:
2990 | for fn in info:
2991 | if not os.path.exists(fn):
2992 | update = True
2993 | break
2994 | oldtime = info[fn]
2995 | newtime = self.mtime(fn)
2996 | if newtime > oldtime:
2997 | update = True
2998 | #print('%f %f %f'%(newtime, oldtime, newtime - oldtime))
2999 | break
3000 | if update:
3001 | dependence = self._scan_src(srcname)
3002 | # print(srcname, dependence)
3003 | info = {}
3004 | self._depinfo[srcname] = info
3005 | if not dependence:
3006 | return -2
3007 | for fname, mtime in dependence:
3008 | info[fname] = mtime
3009 | info = self._depinfo[srcname]
3010 | for fn in info:
3011 | oldtime = info[fn]
3012 | if oldtime > objtime:
3013 | self._dirty[srcname] = 1
3014 | retval = 1
3015 | break
3016 | if debug: print('\n'%srcname)
3017 | return retval
3018 |
3019 | def _load_dep (self):
3020 | lineno = -1 # noqa: F841
3021 | retval = 0
3022 | if os.path.exists(self._depname):
3023 | for line in open(self._depname):
3024 | line = line.strip(' \t\r\n')
3025 | if not line: continue
3026 | pos = line.find('=')
3027 | if pos < 0: continue
3028 | src, body = line[:pos], line[pos + 1:]
3029 | src = os.path.abspath(src)
3030 | if not os.path.exists(src): continue
3031 | item = body.replace(';', ',').split(',')
3032 | count = int(len(item) / 2)
3033 | info = {}
3034 | self._depinfo[src] = info
3035 | for i in range(count):
3036 | fname = item[i * 2 + 0].strip(' \r\n\t')
3037 | mtime = item[i * 2 + 1].strip(' \r\n\t')
3038 | fname = self.parser.pathconf(fname)
3039 | info[fname] = float(mtime)
3040 | retval = 0
3041 | for fn in self.parser:
3042 | self._update_dep(fn)
3043 | return retval
3044 |
3045 | def _save_dep (self):
3046 | path = os.path.split(self._depname)[0]
3047 | if not os.path.exists(path):
3048 | self.parser.coremake.mkdir(path)
3049 | fp = open(self._depname, 'w')
3050 | names = self._depinfo.keys()
3051 | sorted(names)
3052 | for src in names:
3053 | info = self._depinfo[src]
3054 | fp.write('%s = '%(src))
3055 | part = []
3056 | keys = info.keys()
3057 | sorted(keys)
3058 | for fname in keys:
3059 | mtime = info[fname]
3060 | if ' ' in fname: fname = '"%s"'%fname
3061 | part.append('%s, %.6f'%(fname, mtime))
3062 | fp.write(', '.join(part) + '\n')
3063 | fp.close()
3064 | return 0
3065 |
3066 | def __contains__ (self, srcname):
3067 | return (srcname in self._depinfo)
3068 |
3069 | def __getitem__ (self, srcname):
3070 | return self._depinfo[srcname]
3071 |
3072 | def process (self):
3073 | self.reset()
3074 | parser = self.parser
3075 | depname = parser.name + '.p'
3076 | self._depname = os.path.join(parser.home, depname)
3077 | if parser.int:
3078 | self._depname = os.path.join(parser.int, depname)
3079 | self._depname = os.path.abspath(self._depname)
3080 | self._load_dep()
3081 | self._save_dep()
3082 | for info in self._depinfo:
3083 | dirty = (info in self._dirty) and 1 or 0 # noqa: F841
3084 | # print(info, '=', dirty)
3085 | return 0
3086 |
3087 |
3088 | #----------------------------------------------------------------------
3089 | # emake: 工程编译,Compile/Link/Build
3090 | #----------------------------------------------------------------------
3091 | class emake (object):
3092 |
3093 | def __init__ (self, ininame = ''):
3094 | if ininame == '': ininame = 'emake.ini'
3095 | self.parser = iparser(ininame)
3096 | self.coremake = self.parser.coremake
3097 | self.dependence = dependence(self.parser)
3098 | self.config = self.coremake.config
3099 | self.unix = self.coremake.unix
3100 | self.cpus = -1
3101 | self.loaded = 0
3102 | self.error_checked = None
3103 |
3104 | def reset (self):
3105 | self.parser.reset()
3106 | self.coremake.reset()
3107 | self.dependence.reset()
3108 | self.loaded = 0
3109 | self.error_checked = None
3110 |
3111 | def open (self, makefile, profile = None):
3112 | self.reset()
3113 | self.config.init()
3114 | self.config.profile = profile
3115 | environ = {}
3116 | cfg = self.config.config
3117 | if 'environ' in cfg:
3118 | for k, v in cfg['environ'].items():
3119 | environ[k.upper()] = v
3120 | retval = self.parser.parse(makefile, profile)
3121 | if retval != 0:
3122 | return -1
3123 | parser = self.parser
3124 | self.coremake.init(makefile, parser.out, parser.mode, parser.int, profile)
3125 | #print('open', parser.out, parser.mode, parser.int)
3126 | for src in self.parser:
3127 | obj = self.parser[src]
3128 | opt = self.parser.optdict[src]
3129 | self.coremake.push(src, obj, opt)
3130 | savedir = os.getcwd()
3131 | os.chdir(os.path.dirname(os.path.abspath(makefile)))
3132 | hr = self._config()
3133 | os.chdir(savedir)
3134 | if hr != 0:
3135 | return -2
3136 | self.coremake._environ = {}
3137 | for k, v in environ.items():
3138 | self.coremake._environ[k] = v
3139 | for k, v in self.parser.environ.items():
3140 | self.coremake._environ[k] = v
3141 | self.dependence.process()
3142 | self.loaded = 1
3143 | return 0
3144 |
3145 | def _config (self):
3146 | self.config.replace['makefile'] = self.coremake._main
3147 | self.config.replace['workspace'] = os.path.dirname(self.coremake._main)
3148 | for name, fname, lineno in self.parser.imp:
3149 | if name not in self.config.config:
3150 | self.parser.error('error: %s: No such config section'%name,
3151 | fname, lineno)
3152 | return -1
3153 | self.config.loadcfg(self.config.profile, name, True)
3154 | for inc in self.parser.inc:
3155 | self.config.push_inc(inc)
3156 | #print('inc', inc)
3157 | for lib in self.parser.lib:
3158 | self.config.push_lib(lib)
3159 | #print('lib', lib)
3160 | for flag in self.parser.flag:
3161 | self.config.push_flag(flag)
3162 | for link in self.parser.link:
3163 | self.config.push_link(link)
3164 | #print('link', link)
3165 | for pdef in self.parser.define:
3166 | self.config.push_pdef(pdef)
3167 | #print('pdef', pdef)
3168 | for flnk in self.parser.flnk:
3169 | self.config.push_flnk(flnk)
3170 | #print('flnk', flnk)
3171 | for wlnk in self.parser.wlnk:
3172 | self.config.push_wlnk(wlnk)
3173 | for cond in self.parser.cond:
3174 | self.config.push_cond(cond[0], cond[1])
3175 | if self.parser.mode == 'dll' and self.config.unix:
3176 | if self.config.fpic:
3177 | self.config.push_flag('-fPIC')
3178 | if self.parser.pkg:
3179 | hr = self._pkg_config()
3180 | if hr != 0:
3181 | return hr
3182 | for name, fname, lineno in self.parser.exp:
3183 | self.coremake.dllwrap(name)
3184 | self.config.parameters()
3185 | #print('replace', self.config.replace)
3186 | return 0
3187 |
3188 | # run pkg-config
3189 | def _pkg_config (self):
3190 | if not self.parser.pkg:
3191 | return 0
3192 | flags = ' '.join([n[0] for n in self.parser.pkgflag])
3193 | pkgs = ' '.join([n[0] for n in self.parser.pkg])
3194 | text = self.config._getitem('default', 'pcflag', '').strip()
3195 | conf = ' '.join([t.strip() for t in text.split(',')])
3196 | parameter = flags + ' ' + conf.strip() + ' ' + pkgs
3197 | p1 = '--cflags-only-I ' + parameter
3198 | p2 = '--libs ' + parameter
3199 | fname = self.parser.pkg[0][1]
3200 | flnum = self.parser.pkg[0][2]
3201 | printcmd = False
3202 | code, text1 = self.coremake.config.pkgconfig(p1, printcmd, True)
3203 | if code != 0:
3204 | for line in text1.split('\n'):
3205 | line = line.rstrip('\r\n\t ')
3206 | if line:
3207 | msg = 'error: %s'%(line)
3208 | self.parser.error(msg, fname, flnum)
3209 | return -2
3210 | code, text2 = self.coremake.config.pkgconfig(p2, printcmd, True)
3211 | if code != 0:
3212 | for line in text1.split('\n'):
3213 | line = line.rstrip('\r\n\t ')
3214 | if line:
3215 | msg = 'error: %s'%(line)
3216 | self.parser.error(msg, fname, flnum)
3217 | return -3
3218 | text1 = text1.strip('\r\n\t ')
3219 | text2 = text2.strip('\r\n\t ')
3220 | if text1:
3221 | self.config.push_flag(text1)
3222 | if text2:
3223 | self.config.push_flnk(text2)
3224 | if 0:
3225 | print('pkg-inc: %s'%(text1))
3226 | print('pkg-lib: %s'%(text2))
3227 | return 0
3228 |
3229 | def _check_error (self):
3230 | if not self.loaded:
3231 | return 1
3232 | if self.error_checked is None:
3233 | self.error_checked = self.parser.check_error()
3234 | if self.error_checked != 0:
3235 | return 2
3236 | return 0
3237 |
3238 | def compile (self, printmode = 0):
3239 | if not self.loaded:
3240 | return 1
3241 | dirty = 0
3242 | for src in self.parser:
3243 | if src in self.dependence._dirty:
3244 | obj = self.parser[src]
3245 | if obj != src:
3246 | self.coremake.remove(obj)
3247 | dirty += 1
3248 | if dirty:
3249 | self.coremake.remove(self.parser.out)
3250 | self.coremake.event(self.parser.events.get('prebuild', []))
3251 | sys.stdout.flush()
3252 | if self._check_error() != 0:
3253 | return 2
3254 | cpus = self.config.cpus
3255 | if self.cpus >= 0:
3256 | cpus = self.cpus
3257 | retval = self.coremake.compile(True, printmode, cpus)
3258 | if retval != 0:
3259 | return 3
3260 | return 0
3261 |
3262 | def link (self, printmode = 0):
3263 | if not self.loaded:
3264 | return 1
3265 | if self._check_error() != 0:
3266 | return 2
3267 | update = False
3268 | outname = self.parser.out
3269 | outtime = self.dependence.mtime(outname)
3270 | for src in self.parser:
3271 | obj = self.parser[src]
3272 | mtime = self.dependence.mtime(obj)
3273 | if mtime == 0 or mtime > outtime:
3274 | update = True
3275 | break
3276 | if update:
3277 | self.coremake.remove(self.parser.out)
3278 | self.coremake.event(self.parser.events.get('prelink', []))
3279 | retval = self.coremake.link(True, printmode)
3280 | if retval:
3281 | self.coremake.event(self.parser.events.get('postbuild', []))
3282 | return 0
3283 | return 3
3284 |
3285 | def build (self, printmode = 0):
3286 | if not self.loaded:
3287 | return 1
3288 | retval = self.compile(printmode)
3289 | if retval != 0:
3290 | return 3
3291 | retval = self.link(printmode)
3292 | if retval != 0:
3293 | return 4
3294 | return 0
3295 |
3296 | def clean (self):
3297 | if not self.loaded:
3298 | return 1
3299 | for src in self.parser:
3300 | obj = self.parser[src]
3301 | if obj != src:
3302 | self.coremake.remove(obj)
3303 | if self.loaded:
3304 | self.coremake.remove(self.parser.out)
3305 | return 0
3306 |
3307 | def rebuild (self, printmode = -1):
3308 | if not self.loaded:
3309 | return 1
3310 | self.clean()
3311 | return self.build(printmode)
3312 |
3313 | def execute (self):
3314 | if not self.loaded:
3315 | return 1
3316 | outname = os.path.abspath(self.parser.out)
3317 | if self.parser.mode not in ('exe', 'win'):
3318 | sys.stderr.write('cannot execute: \'%s\'\n'%outname)
3319 | sys.stderr.flush()
3320 | return 8
3321 | if not os.path.exists(outname):
3322 | sys.stderr.write('cannot find: \'%s\'\n'%outname)
3323 | sys.stderr.flush()
3324 | return 9
3325 | os.system('"%s"'%outname)
3326 | return 0
3327 |
3328 | def call (self, cmdline):
3329 | if not self.loaded:
3330 | return 1
3331 | self.coremake.event([cmdline])
3332 | return 0
3333 |
3334 | def info (self, name = ''):
3335 | name = name.lower()
3336 | if name == '': name = 'out'
3337 | if name in ('out', 'outname'):
3338 | print(self.parser.out)
3339 | elif name in ('home', 'base'):
3340 | print(self.parser.home)
3341 | elif name in ('list'):
3342 | for src in self.parser:
3343 | print(src)
3344 | elif name in ('dirty', 'changed'):
3345 | for src in self.parser:
3346 | if src in self.dependence._dirty:
3347 | print(src)
3348 | elif name in ('cflags',):
3349 | for src in self.parser:
3350 | print('%s: %s'%(src, self.parser.compile_flag(src)))
3351 | elif name in ('depends', 'dependencies'):
3352 | for src in self.parser:
3353 | info = self.dependence[src]
3354 | head = [ key for key in info ]
3355 | print('%s: %s'%(src, ', '.join(head)))
3356 | elif name in ('objs',):
3357 | for src in self.parser:
3358 | print('%s: %s'%(src, self.parser[src]))
3359 | elif name in ('commands', 'command'):
3360 | cc = self.config.exename.get('gcc', 'gcc')
3361 | info = []
3362 | home = self.parser.home
3363 | for src in self.parser:
3364 | relname = self.config.pathrel(src, home)
3365 | outname = self.config.pathrel(self.parser[src], home)
3366 | cmd = cc + ' ' + self.parser.compile_flag(src) + ' '
3367 | cmd += '-o ' + self.config.pathtext(outname) + ' '
3368 | cmd += self.config.pathtext(relname)
3369 | item = {}
3370 | item['directory'] = home.replace('\\', '/')
3371 | item['command'] = cmd
3372 | item['file'] = self.config.pathrel(src, home).replace('\\', '/')
3373 | item['output'] = self.config.pathrel(self.parser[src], home)
3374 | info.append(item)
3375 | import json
3376 | print(json.dumps(info, indent = 4))
3377 | return 0
3378 |
3379 |
3380 |
3381 | #----------------------------------------------------------------------
3382 | # speed up
3383 | #----------------------------------------------------------------------
3384 | def _psyco_speedup():
3385 | try:
3386 | import psyco
3387 | psyco.bind(preprocessor)
3388 | psyco.bind(configure)
3389 | psyco.bind(coremake)
3390 | psyco.bind(emake)
3391 | #print('full optimaze')
3392 | except:
3393 | return False
3394 | return True
3395 |
3396 |
3397 |
3398 | #----------------------------------------------------------------------
3399 | # distribution
3400 | #----------------------------------------------------------------------
3401 | def install():
3402 | filepath = os.path.abspath(sys.argv[0])
3403 | if not os.path.exists(filepath):
3404 | print('error: cannot open "%s"'%filepath)
3405 | return -1
3406 | if sys.platform[:3] == 'win':
3407 | print('error: install must under unix')
3408 | return -2
3409 | try:
3410 | f1 = open(filepath, 'rb')
3411 | except:
3412 | print('error: cannot read "%s"'%filepath)
3413 | return -3
3414 | content = f1.read()
3415 | f1.close()
3416 | name2 = '/usr/local/bin/emake.py'
3417 | name3 = '/usr/local/bin/emake'
3418 | if os.path.exists(name2):
3419 | print('/usr/local/bin/emake.py already exists, you should delete it')
3420 | return -6
3421 | if os.path.exists(name3):
3422 | print('/usr/local/bin/emake already exists, you should delete it')
3423 | return -7
3424 | try:
3425 | f2 = open(name2, 'wb')
3426 | except:
3427 | print('error: cannot write "%s"'%name2)
3428 | return -4
3429 | try:
3430 | f3 = open(name3, 'wb')
3431 | except:
3432 | print('error: cannot write "%s"'%name3)
3433 | f2.close()
3434 | return -5
3435 | f2.write(content)
3436 | f3.write(content)
3437 | f2.close()
3438 | f3.close()
3439 | os.system('chmod 755 /usr/local/bin/emake.py')
3440 | os.system('chmod 755 /usr/local/bin/emake')
3441 | os.system('chown root /usr/local/bin/emake.py 2> /dev/null')
3442 | os.system('chown root /usr/local/bin/emake 2> /dev/null')
3443 | print('install completed. you can uninstall by deleting the following two files:')
3444 | print('/usr/local/bin/emake.py')
3445 | print('/usr/local/bin/emake')
3446 | return 0
3447 |
3448 | __updated_files = {}
3449 |
3450 | def __update_file(name, content):
3451 | source = b''
3452 | name = os.path.abspath(name)
3453 | if name in __updated_files:
3454 | return 0
3455 | __updated_files[name] = 1
3456 | try:
3457 | fp = open(name, 'rb')
3458 | source = fp.read()
3459 | fp.close()
3460 | except:
3461 | source = b''
3462 | if not isinstance(content, bytes):
3463 | rawdata = content.encoding('utf-8', 'ignore')
3464 | else:
3465 | rawdata = content
3466 | if rawdata.rstrip(b'\r\n\t ') == source.rstrip(b'\r\n\t '):
3467 | print('%s up-to-date'%name)
3468 | return 0
3469 | try:
3470 | fp = open(name, 'wb')
3471 | fp.write(rawdata)
3472 | fp.close()
3473 | except:
3474 | print('can not write to %s'%name)
3475 | return -1
3476 | print('%s update succeeded'%name)
3477 | return 1
3478 |
3479 | def getemake():
3480 | url1 = 'https://skywind3000.github.io/emake/emake.py'
3481 | url2 = 'https://www.skywind.me/php/getemake.php'
3482 | success = True
3483 | content = ''
3484 | signature = ''
3485 | for url in (url1, url2):
3486 | print('fetching', url, ' ...',)
3487 | sys.stdout.flush()
3488 | success = True
3489 | if sys.version_info[0] < 3:
3490 | import urllib2
3491 | try:
3492 | content = urllib2.urlopen(url).read()
3493 | signature = content.split('\n')[0].strip('\r\n\t ')
3494 | except urllib2.URLError as e:
3495 | success = False
3496 | print('failed ')
3497 | print(e)
3498 | else:
3499 | import urllib.request
3500 | try:
3501 | with urllib.request.urlopen(url) as response:
3502 | content = response.read()
3503 | t = content.split(b'\n')[0].strip(b'\r\n\t ')
3504 | signature = t.decode('ascii', 'ignore')
3505 | except urllib.error.URLError as e:
3506 | success = False
3507 | print('failed ')
3508 | print(e)
3509 | if not signature.startswith('#! /usr/bin/env python'):
3510 | if success:
3511 | print('error')
3512 | success = False
3513 | if success:
3514 | print('ok')
3515 | return content
3516 | return ''
3517 |
3518 | def update():
3519 | content = getemake()
3520 | if not content:
3521 | print('update failed')
3522 | return -1
3523 | name1 = os.path.abspath(sys.argv[0])
3524 | name2 = '/usr/local/bin/emake.py'
3525 | name3 = '/usr/local/bin/emake'
3526 | __update_file(name1, content)
3527 | if sys.platform[:3] == 'win':
3528 | return 0
3529 | r1 = __update_file(name2, content)
3530 | r2 = __update_file(name3, content)
3531 | if r1 > 0:
3532 | os.system('chmod 755 /usr/local/bin/emake.py')
3533 | os.system('chown root /usr/local/bin/emake.py 2> /dev/null')
3534 | if r2 > 0:
3535 | os.system('chmod 755 /usr/local/bin/emake')
3536 | os.system('chown root /usr/local/bin/emake 2> /dev/null')
3537 | print('update finished !')
3538 | return 0
3539 |
3540 | def help():
3541 | version = '(emake %s %s %s)'%(EMAKE_VERSION, EMAKE_DATE, sys.platform)
3542 | print('usage: "emake.py [options] srcfile" %s'%version)
3543 | print('')
3544 | print('actions : -b | -build build project')
3545 | print(' -c | -compile compile project')
3546 | print(' -l | -link link project')
3547 | print(' -r | -rebuild rebuild project')
3548 | print(' -e | -execute execute project')
3549 | print(' -o | -out show output file name')
3550 | print(' -d | -cmdline call cmdline tool in given environ')
3551 | if sys.platform[:3] == 'win':
3552 | print(' -g | -cygwin cygwin execute')
3553 | print(' -s | -cshell cygwin shell')
3554 | print(' -i | -install install emake on unix')
3555 | print(' -u | -update update itself from github')
3556 | print(' -h | -help show help page')
3557 | print('')
3558 | print(' -home display project home')
3559 | print(' -list display project files')
3560 | print(' -objs display obj files')
3561 | print(' -cflags display compile flags')
3562 | print(' -depends display dependencies')
3563 | print(' -dirty display dirty files')
3564 | print(' -commands display compile commands json')
3565 | print('')
3566 | print('options : --cfg={cfg} load config from ~/.config/emake/{cfg}.ini')
3567 | print(' --ini={inipath} load config from {inipath} directly')
3568 | print(' --profile={name} set profile to {name}')
3569 | print(' --print={n} set verbose level: 0-7')
3570 | print(' --abs={0|1} display absolute path in error messages')
3571 | print('')
3572 | print("Emake is a easy tool which controls the generation of executables and other")
3573 | print("non-source files of a program from the program's source files. ")
3574 | print('')
3575 | print('Homepage: https://github.com/skywind3000/emake')
3576 | print('')
3577 | return 0
3578 |
3579 |
3580 | #----------------------------------------------------------------------
3581 | # extract param
3582 | #----------------------------------------------------------------------
3583 | def extract(parameter):
3584 | if parameter[:2] != '${' or parameter[-1:] != '}':
3585 | return parameter
3586 | data = parameter[2:-1]
3587 | pos = data.find(':')
3588 | if pos < 0:
3589 | return parameter
3590 | fname, cname = data[:pos], data[pos + 1:]
3591 | if not os.path.exists(fname):
3592 | return parameter
3593 | parser = iparser()
3594 | command = parser._scan_memo(fname)
3595 | value = ''
3596 | for lineno, text in command:
3597 | pos = text.find(':')
3598 | if pos >= 0:
3599 | name, data = text[:pos], text[pos + 1:]
3600 | name = name.strip('\r\n\t ')
3601 | if name == cname:
3602 | value = data.strip('\r\n\t ')
3603 | return value
3604 |
3605 |
3606 | #----------------------------------------------------------------------
3607 | # main program
3608 | #----------------------------------------------------------------------
3609 | def main(argv = None):
3610 | # using psyco to speed up
3611 | _psyco_speedup()
3612 |
3613 | # create main object
3614 | make = emake()
3615 |
3616 | if argv is None:
3617 | argv = sys.argv
3618 |
3619 | args = argv
3620 | argv = argv[:1]
3621 | options = {}
3622 |
3623 | for arg in args[1:]:
3624 | if arg[:2] != '--':
3625 | argv.append(arg)
3626 | continue
3627 | key = arg[2:].strip('\r\n\t ')
3628 | val = None
3629 | p1 = key.find('=')
3630 | if p1 >= 0:
3631 | val = key[p1 + 1:].strip('\r\n\t')
3632 | key = key[:p1].strip('\r\n\t')
3633 | options[key] = val
3634 |
3635 | inipath = ''
3636 |
3637 | if options.get('cfg', None) is not None:
3638 | cfg = options['cfg'].strip('\r\n\t ')
3639 | if cfg:
3640 | cfg = os.path.expanduser('~/.config/emake/%s.ini'%cfg)
3641 | if 'ini' not in options:
3642 | options['ini'] = cfg
3643 | elif 'EMAKE_CFG' in os.environ:
3644 | cfg = os.environ['EMAKE_CFG']
3645 | cfg = str(cfg).strip('\r\n\t ')
3646 | if ('ini' not in options) and cfg:
3647 | options['ini'] = cfg
3648 |
3649 | if options.get('ini', None) is not None:
3650 | inipath = options['ini']
3651 | if '~' in inipath:
3652 | inipath = os.path.expanduser(inipath)
3653 | inipath = os.path.abspath(inipath)
3654 |
3655 | if len(argv) <= 1:
3656 | version = '(emake %s %s %s)'%(EMAKE_VERSION, EMAKE_DATE, sys.platform)
3657 | print('usage: "emake.py [options] srcfile" %s'%version)
3658 | print('actions : -b | -build build project')
3659 | print(' -c | -compile compile project')
3660 | print(' -l | -link link project')
3661 | print(' -r | -rebuild rebuild project')
3662 | print(' -e | -execute execute project')
3663 | print(' -o | -out show output file name')
3664 | print(' -d | -cmdline call cmdline tool in given environ')
3665 | if sys.platform[:3] == 'win':
3666 | print(' -g | -cygwin cygwin execute')
3667 | print(' -s | -cshell cygwin shell')
3668 | print(' -i | -install install emake on unix')
3669 | print(' -u | -update update itself from github')
3670 | print(' -h | -help show help page')
3671 | return 0
3672 |
3673 | if inipath and os.path.exists(inipath):
3674 | global INIPATH
3675 | INIPATH = inipath
3676 | elif inipath:
3677 | sys.stderr.write('error: not find %s\n'%inipath)
3678 | sys.stderr.flush()
3679 | return -1
3680 |
3681 | if argv[1] == '-check':
3682 | make.config.init()
3683 | make.config.check()
3684 | dirhome = make.config.dirhome
3685 | print('home:', dirhome)
3686 | print('gcc:', os.path.join(dirhome, make.config.exename['gcc']))
3687 | print('name:', make.config.name.keys())
3688 | print('target:', make.config.target)
3689 | return 0
3690 |
3691 | cmd, name = 'build', ''
3692 |
3693 | if len(argv) == 2:
3694 | name = argv[1].strip(' ')
3695 | if name in ('-i', '-install', '-install'):
3696 | install()
3697 | return 0
3698 | if name in ('-u', '-update', '-update'):
3699 | update()
3700 | return 0
3701 | if name in ('-h', '-help', '-help'):
3702 | help()
3703 | return 0
3704 |
3705 | if len(argv) <= 3:
3706 | if name in ('-d', '-cmdline'):
3707 | print('usage: emake.py -cmdline envname exename [parameters]')
3708 | print('call the cmdline tool in the given environment:')
3709 | print('- envname is a section name in emake.ini which defines environ for this tool')
3710 | print('- exename is the tool\'s executable file name')
3711 | return 0
3712 |
3713 | if len(argv) >= 3:
3714 | cmd = argv[1].strip(' ').lower()
3715 | name = argv[2]
3716 | else:
3717 | if name[:1] == '-':
3718 | print('not enough parameter: %s'%name)
3719 | return 0
3720 |
3721 | printmode = PRINT_MODE_BASIC
3722 |
3723 | def int_safe(text, defval):
3724 | num = defval
3725 | try: num = int(text)
3726 | except: pass
3727 | return num
3728 |
3729 | def bool_safe(text, defval):
3730 | if text is None:
3731 | return True
3732 | if text.lower() in ('true', '1', 'yes', 't'):
3733 | return True
3734 | if text.lower() in ('0', 'false', 'no', 'n'):
3735 | return False
3736 | return defval
3737 |
3738 | if 'cpu' in options:
3739 | make.cpus = int_safe(options['cpu'], 1)
3740 |
3741 | if 'print' in options:
3742 | printmode = int_safe(options['print'], PRINT_MODE_BASIC)
3743 |
3744 | if 'int' in options:
3745 | CFG['int'] = options['int']
3746 |
3747 | printenv = os.environ.get('EMAKE_PRINT', '')
3748 |
3749 | if printenv:
3750 | printmode = int_safe(printenv, PRINT_MODE_BASIC)
3751 |
3752 | if 'EMAKE_ABSPATH' in os.environ:
3753 | hr = bool_safe(os.environ['EMAKE_ABSPATH'], True)
3754 | CFG['abspath'] = hr
3755 |
3756 | if 'abs' in options:
3757 | CFG['abspath'] = bool_safe(options['abs'], True)
3758 |
3759 | ext = os.path.splitext(name)[-1].lower()
3760 | ft1 = ('.c', '.cpp', '.cxx', '.cc', '.m', '.mm')
3761 | ft2 = ('.h', '.hpp', '.hxx', '.hh', '.inc') # noqa: F841
3762 | ft3 = ('.mak', '.em', '.emk', '.py', '.pyx')
3763 |
3764 | if cmd in ('-d', '-cmdline', '-cmdline', '-m'):
3765 | config = configure()
3766 | config.init()
3767 | argv += ['', '', '', '', '']
3768 | envname = argv[2]
3769 | parameters = ''
3770 | for n in [ argv[i] for i in range(3, len(argv)) ]:
3771 | if cmd in ('-m',):
3772 | if n[:2] == '${' and n[-1:] == '}':
3773 | n = extract(n)
3774 | if not n: continue
3775 | if config.unix:
3776 | n = n.replace('\\', '\\\\').replace('"', '\\"')
3777 | n = n.replace("'", "\\'").replace(' ', '\\ ')
3778 | n = n.replace('\t', '\\t')
3779 | else:
3780 | if ' ' in n:
3781 | n = '"' + n + '"'
3782 | parameters += n + ' '
3783 | hr = config.cmdtool(envname, parameters.rstrip(' '))
3784 | sys.exit(hr)
3785 | return 0
3786 |
3787 | if cmd in ('-g', '-cygwin'):
3788 | config = configure()
3789 | config.init()
3790 | if not config.cygwin:
3791 | print('not find "cygwin" in "default" sect of %s'%config.ininame)
3792 | sys.exit()
3793 | argv += ['', '', '', '', '']
3794 | envname = argv[2]
3795 | exename = argv[3]
3796 | parameters = ''
3797 | for n in [ argv[i] for i in range(4, len(argv)) ]:
3798 | if ' ' in n: n = '"' + n + '"'
3799 | parameters += n + ' '
3800 | config.cygwin_execute(envname, exename, parameters)
3801 | return 0
3802 |
3803 | if cmd in ('-s', '-cshell'):
3804 | config = configure()
3805 | config.init()
3806 | if not config.cygwin:
3807 | print('not find "cygwin" in "default" sect of %s'%config.ininame)
3808 | sys.exit()
3809 | argv += ['', '', '', '', '']
3810 | envname = argv[2]
3811 | exename = argv[3]
3812 | parameters = ''
3813 | for n in [ argv[i] for i in range(4, len(argv)) ]:
3814 | if ' ' in n: n = '"' + n + '"'
3815 | parameters += n + ' '
3816 | cmds = '"%s" %s'%(exename, parameters)
3817 | config.cygwin_execute(envname, '', cmds)
3818 | return 0
3819 |
3820 | if cmd in ('-z', '-shell'):
3821 | config = configure()
3822 | config.init()
3823 | args = []
3824 | args.extend(argv[2:])
3825 | execute(args, True, False)
3826 | code = os.shell_return
3827 | if code < 0:
3828 | code = 128
3829 | return sys.exit(code)
3830 |
3831 | if cmd == '-dump':
3832 | if not name: name = '.'
3833 | if not os.path.exists(name):
3834 | print('can not read: %s'%name)
3835 | return -1
3836 | for root, dirs, files in os.walk(name):
3837 | for fn in files:
3838 | if os.path.splitext(fn)[-1].lower() in ('.c', '.cpp', '.cc'):
3839 | xp = os.path.join(root, fn)
3840 | if sys.platform[:3] == 'win':
3841 | xp = xp.replace('\\', '/')
3842 | if xp[:2] == './':
3843 | xp = xp[2:]
3844 | print('src: ' + xp)
3845 | if 'CVS' in dirs:
3846 | dirs.remove('CVS') # don't visit CVS directories
3847 | if '.svn' in dirs:
3848 | dirs.remove('.svn')
3849 | if '.git' in dirs:
3850 | dirs.remove('.git')
3851 | return 0
3852 |
3853 | if not ((ext in ft1) or (ext in ft3)):
3854 | sys.stderr.write('error: %s: unsupported file type\n'%(name))
3855 | sys.stderr.flush()
3856 | return -1
3857 |
3858 | retval = 0
3859 |
3860 | profile = os.environ.get('EMAKE_PROFILE', '')
3861 |
3862 | if not profile:
3863 | profile = options.get('profile', '')
3864 |
3865 | if cmd in ('b', '-b', 'build', '-build'):
3866 | make.open(name, profile)
3867 | retval = make.build(printmode)
3868 | elif cmd in ('c', '-c', 'compile', '-compile'):
3869 | make.open(name, profile)
3870 | retval = make.compile(printmode)
3871 | elif cmd in ('l', '-l', 'link', '-link'):
3872 | make.open(name, profile)
3873 | retval = make.link(printmode)
3874 | elif cmd in ('clean', '-clean'):
3875 | make.open(name, profile)
3876 | retval = make.clean()
3877 | elif cmd in ('r', '-r', 'rebuild', '-rebuild'):
3878 | make.open(name, profile)
3879 | retval = make.rebuild(printmode)
3880 | elif cmd in ('e', '-e', 'execute', '-execute'):
3881 | make.open(name, profile)
3882 | retval = make.execute()
3883 | elif cmd in ('a', '-a', 'call', '-call'):
3884 | make.open(name, profile)
3885 | retval = make.call(' '.join(argv[3:]))
3886 | elif cmd in ('o', '-o', 'out', '-out'):
3887 | make.open(name, profile)
3888 | make.info('outname')
3889 | elif cmd in ('dirty', '-dirty'):
3890 | make.open(name, profile)
3891 | make.info('dirty')
3892 | elif cmd in ('list', '-list'):
3893 | make.open(name, profile)
3894 | make.info('list')
3895 | elif cmd in ('home', '-home'):
3896 | make.open(name, profile)
3897 | make.info('home')
3898 | elif cmd in ('-objs'):
3899 | make.open(name, profile)
3900 | make.info('objs')
3901 | elif cmd in ('-cflags'):
3902 | make.open(name, profile)
3903 | make.info('cflags')
3904 | elif cmd in ('-depends'):
3905 | make.open(name, profile)
3906 | make.info('depends')
3907 | elif cmd in ('-commands', '-command'):
3908 | make.open(name, profile)
3909 | make.info('command')
3910 | else:
3911 | sys.stderr.write('unknow command: %s\n'%cmd)
3912 | sys.stderr.flush()
3913 | retval = 127
3914 | return retval
3915 |
3916 |
3917 | #----------------------------------------------------------------------
3918 | # testing case
3919 | #----------------------------------------------------------------------
3920 | if __name__ == '__main__':
3921 | def test1():
3922 | make = coremake()
3923 | name = 'e:/zombie/demo01.c' # noqa: F841
3924 | make.mkdir(r'e:\lab\malloc\obj list')
3925 | make.mkdir(r'e:\lab\malloc\abc c\01 2\3 4\5\6')
3926 | make.init('mainmod', 'exe', 'malloc/obj')
3927 | make.push('malloc/main.c', '', {})
3928 | make.push('malloc/mod1.c', '', {})
3929 | make.push('malloc/mod2.c', '', {})
3930 | make.push('malloc/mod3.c', '', {})
3931 | make.build(printmode = PRINT_MODE_ALL)
3932 | print(os.path.getmtime('malloc/main.c'))
3933 | def test2():
3934 | pst = preprocessor()
3935 | head, lost, text = pst.dependence('voice/fastvoice/basewave.cpp')
3936 | for n in head: print(n)
3937 | pp = pst.preprocess(open('voice/fastvoice/basewave.cpp').read())
3938 | print(pp)
3939 | def test3():
3940 | parser = iparser()
3941 | parser._pragma_scan('malloc/main.c')
3942 | def test4():
3943 | parser = iparser()
3944 | cmaker = coremake()
3945 | parser.parse('malloc/main.c')
3946 | print('"%s", "%s", "%s"'%(parser.out, parser.int, parser.mode))
3947 | print(parser.home, parser.name)
3948 | for n in parser:
3949 | print('src:', n, '->', cmaker.objname(n, ''), parser[n])
3950 | def test5():
3951 | parser = iparser()
3952 | parser.parse('malloc/main.c')
3953 | dep = dependence(parser)
3954 | dep.process()
3955 | def test6():
3956 | make = emake()
3957 | make.open('malloc/main.c')
3958 | make.clean()
3959 | make.build(3)
3960 | def test7():
3961 | config = configure()
3962 | config.init()
3963 | print(config.checklib('liblinwei.a'))
3964 | print(config.checklib('winmm'))
3965 | print(config.checklib('pixia'))
3966 | config.push_lib('d:/dev/local/lib')
3967 | print(config.checklib('pixia'))
3968 | def test8():
3969 | sys.argv = [sys.argv[0], '-d', 'msvc', 'cl.exe', '-help' ]
3970 | sys.argv = [sys.argv[0], '-r', 'd:/acm/aprcode/pixellib/PixelBitmap.cpp' ]
3971 | main()
3972 | #os.chdir('d:/acm/aprcode/pixellib/')
3973 | #os.system('d:/dev/python27/python.exe d:/acm/opensrc/easymake/testing/emake.py -r PixelBitmap.cpp')
3974 | def test9():
3975 | sys.argv = ['emake.py', '-t', 'msvc', 'cl.exe', '-help' ]
3976 | sys.argv = [sys.argv[0], '-t', 'watcom', 'wcl386.exe', '-help' ]
3977 | main()
3978 | def test10():
3979 | sys.argv = [sys.argv[0], '-g', 'default', 'd:/dev/flash/alchemy5/tutorials/01_HelloWorld/hello.exe', '--version']
3980 | main()
3981 | def test11():
3982 | update()
3983 | return 0
3984 |
3985 | # test11()
3986 | sys.exit( main() )
3987 | #install()
3988 |
3989 |
3990 |
3991 |
--------------------------------------------------------------------------------
/history.txt:
--------------------------------------------------------------------------------
1 | May.31 2012 version 3.02
2 | - mac os -dynamiclib support
3 | - using subprocess module instead of popen
4 | - fixed some bug
5 |
6 | May.26 2012 version 3.01
7 | - new: multiprocess build system !!
8 | - new: optimize source preprocessing, speed up 200%
9 | - new: -cmdline supported
10 |
11 |
--------------------------------------------------------------------------------
/sample/build.bat:
--------------------------------------------------------------------------------
1 | ..\emake.py testmain.c
2 | pause
--------------------------------------------------------------------------------
/sample/build.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | python ../emake.py testmain.c
3 |
--------------------------------------------------------------------------------
/sample/testmain.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include "testsrc1.h"
3 |
4 | //! mode: exe
5 | //! flag: -O3
6 | //! link: m
7 | //! out: testmain
8 | //! src: testmain.c, testsrc1.c
9 | int main(void)
10 | {
11 | foo();
12 | return 0;
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/sample/testsrc1.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include "testsrc1.h"
3 |
4 | void foo(void)
5 | {
6 | printf("This is foo\n");
7 | }
8 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/testsrc1.h:
--------------------------------------------------------------------------------
1 | #ifndef __TESTSRC1_H__
2 | #define __TESTSRC1_H__
3 |
4 | void foo(void);
5 |
6 |
7 | #endif
8 |
9 |
10 |
--------------------------------------------------------------------------------