├── .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 | --------------------------------------------------------------------------------