diff options
author | bradnelson@google.com <bradnelson@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-21 01:09:05 +0000 |
---|---|---|
committer | bradnelson@google.com <bradnelson@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-21 01:09:05 +0000 |
commit | 07ffef1a601366619755a4f15cf4aa1b2e9ecfa7 (patch) | |
tree | b88600e931428dbe4f0d28ad1d94732f398ede4c /native_client_sdk/src | |
parent | 0f9067badc4ebce72372d06f1e75550cfb8af945 (diff) | |
download | chromium_src-07ffef1a601366619755a4f15cf4aa1b2e9ecfa7.zip chromium_src-07ffef1a601366619755a4f15cf4aa1b2e9ecfa7.tar.gz chromium_src-07ffef1a601366619755a4f15cf4aa1b2e9ecfa7.tar.bz2 |
Adding in (mostly) verbatim landing of nacl sdk.
Dropping large binaries.
Redo of http://crrev.com/110822
checkdeps + checkperms have now beep updated in preparation.
BUG=None
TEST=None
R=noelallen@google.com
TBR
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110872 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'native_client_sdk/src')
283 files changed, 37511 insertions, 0 deletions
diff --git a/native_client_sdk/src/.gitignore b/native_client_sdk/src/.gitignore new file mode 100644 index 0000000..05bda61 --- /dev/null +++ b/native_client_sdk/src/.gitignore @@ -0,0 +1,33 @@ +# This is a standard .gitignore file, used to exclude certain files +# from a 'git status' command, or similar + +# Ignore these filename patterns +*.cmd +*.gz +*.nexe +*.nmf +*.o +*.pyc +*.pyo + +# Ignore all hidden files +.* + +# Ignore all directories than contain these names +scons-out/ +third_party/ +toolchain/ + +# Ignore these particular directories (relative to src directory) +/build_tools/NSIS/ +/build_tools/packages/ +/build_tools/sdk_install_name.nsh +/build_tools/sdk_section.nsh +/build_tools/tests/apply_patch_test_archive/ +/build_tools/toolchain_archives/ +/chrome_binaries/ +/debugger/NativeClientVSX/ +/debugger/nacl-gdb_server/x64/ +/experimental/webgtt/tests/nacltest/*.cc +/site_scons/ +/staging/ diff --git a/native_client_sdk/src/AUTHORS b/native_client_sdk/src/AUTHORS new file mode 100644 index 0000000..24df0cd --- /dev/null +++ b/native_client_sdk/src/AUTHORS @@ -0,0 +1,7 @@ +# Below is a list of people and organizations that have contributed source +# code to the Native Client SDK project. Names should be added to the list +# like this: +# +# Name/Organization <email address> + +Google Inc. native-client-discuss@google.com diff --git a/native_client_sdk/src/COPYING b/native_client_sdk/src/COPYING new file mode 100644 index 0000000..2ebf7c0 --- /dev/null +++ b/native_client_sdk/src/COPYING @@ -0,0 +1,28 @@ +Copyright 2010, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/native_client_sdk/src/DEPS b/native_client_sdk/src/DEPS new file mode 100644 index 0000000..efe627b --- /dev/null +++ b/native_client_sdk/src/DEPS @@ -0,0 +1,124 @@ +vars = { + "chrome_milestone": "17", + "chrome_rev": "109032", + "chromium_trunk": "http://src.chromium.org/svn/trunk", + # Get PPAPI directly from Chrome, not via the NaCl repo. + "gmock_trunk": "http://googlemock.googlecode.com/svn/trunk/", + "gmock_version": "382", + "gtest_trunk": "http://googletest.googlecode.com/svn/trunk/", + "gtest_version": "570", + "native_client_trunk": "http://src.chromium.org/native_client/trunk", + "native_client_version": "7141", + # Note: The following version should exactly match the toolchain version in + # the native_client DEPS file at version native_client_version + # TODO(mball) find some clever way to extract this from NaCl DEPS + "pnacl_toolchain_version": "7107", + "x86_toolchain_version": "7098", + # ARM is not supported, this number can stay pinned at 6645. + "arm_trusted_toolchain_version": "6645", + "pymox": "http://pymox.googlecode.com/svn/trunk", + "pymox_version": "61", +} +# When we have ARM and PNaCl support, we'll need to add toolchain support +# for those too. + +deps = { + "nacl_deps": + File(Var("native_client_trunk") + "/src/native_client/DEPS@" + + Var("native_client_version")), + # Please keep these in alphabetical order, by path + "src/site_scons": + Var("native_client_trunk") + "/src/native_client/site_scons@" + + Var("native_client_version"), + "src/third_party/gmock": + Var("gmock_trunk") + "@" + Var("gmock_version"), + "src/third_party/gtest": + Var("gtest_trunk") + "@" + Var("gtest_version"), + "src/third_party/native_client/base": + Var("chromium_trunk") + "/src/base@" + Var("chrome_rev"), + "src/third_party/native_client/build": + Var("chromium_trunk") + "/src/build@" + Var("chrome_rev"), + "src/third_party/native_client/gpu": + Var("chromium_trunk") + "/src/gpu@" + Var("chrome_rev"), + "src/third_party/native_client/native_client": + Var("native_client_trunk") + "/src/native_client/@" + + Var("native_client_version"), + "src/third_party/native_client/ppapi": + Var("chromium_trunk") + "/src/ppapi@" + Var("chrome_rev"), + "src/third_party/pymox": + Var("pymox") + "@" + Var("pymox_version"), + "src/third_party/scons-2.0.1": + Var("native_client_trunk") + "/src/third_party/scons-2.0.1@" + + Var("native_client_version"), + "src/third_party/native_client/third_party/pylib": + Var("native_client_trunk") + "/src/third_party/pylib@" + + Var("native_client_version"), + "src/third_party/native_client/third_party/scons-2.0.1": + Var("native_client_trunk") + "/src/third_party/scons-2.0.1@" + + Var("native_client_version"), + "src/third_party/native_client/third_party/simplejson": + (Var("chromium_trunk") + "/tools/build/third_party/simplejson@" + + Var("chrome_rev")), + "src/third_party/native_client/tools/valgrind": + Var("chromium_trunk") + "/src/tools/valgrind@" + Var("chrome_rev"), +} + +deps_os = { + # Please keep these in alphabetical order, by path + "win": { + "src/experimental/visual_studio_plugin/third_party/native_client/" + "src/shared/gio": + Var("native_client_trunk") + "/src/native_client/src/shared/gio@" + + Var("native_client_version"), + "src/experimental/visual_studio_plugin/third_party/native_client/" + "src/shared/platform": + Var("native_client_trunk") + "/src/native_client/src/shared/platform@" + + Var("native_client_version"), + "src/experimental/visual_studio_plugin/third_party/native_client/" + "src/trusted/debug_stub": + Var("native_client_trunk") + "/src/native_client/src/trusted/debug_stub@" + + Var("native_client_version"), + "src/experimental/visual_studio_plugin/third_party/native_client/" + "src/trusted/service_runtime/include": + Var("native_client_trunk") + + "/src/native_client/src/trusted/service_runtime/include@" + + Var("native_client_version"), + "src/experimental/visual_studio_plugin/third_party/native_client/" + "src/include": + Var("native_client_trunk") + "/src/native_client/src/include@" + + Var("native_client_version"), + "src/experimental/visual_studio_plugin/third_party/native_client/" + "src/trusted/port": + Var("native_client_trunk") + "/src/native_client/src/trusted/port@" + + Var("native_client_version"), + "src/experimental/visual_studio_plugin/third_party/native_client/" + "src/trusted/gdb_rsp": + Var("native_client_trunk") + "/src/native_client/src/trusted/gdb_rsp@" + + Var("native_client_version"), + "src/third_party/native_client/third_party/mingw-w64/mingw/bin": + From("nacl_deps", "third_party/mingw-w64/mingw/bin"), + }, +} + +hooks = [ + { + "pattern": ".", + "action": [ + "python", + "src/third_party/native_client/native_client/build/" + "download_toolchains.py", + "--x86-version", Var("x86_toolchain_version"), + "--pnacl-version", Var("pnacl_toolchain_version"), + "--arm-trusted-version", Var("arm_trusted_toolchain_version"), + "--toolchain-dir", "src/toolchain", + "--save-downloads-dir", "src/build_tools/toolchain_archives", + ] + }, + { + "pattern": ".", + "action": [ + "python", + "src/build_tools/install_third_party.py", "--all-toolchains", + ] + } +] diff --git a/native_client_sdk/src/LICENSE b/native_client_sdk/src/LICENSE new file mode 100644 index 0000000..21b7ca0 --- /dev/null +++ b/native_client_sdk/src/LICENSE @@ -0,0 +1,28 @@ +Copyright 2011, The Native Client Authors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/native_client_sdk/src/NOTICE b/native_client_sdk/src/NOTICE new file mode 100644 index 0000000..d43b2be --- /dev/null +++ b/native_client_sdk/src/NOTICE @@ -0,0 +1,2434 @@ +====================================================================== +native_client_sdk is licensed as follows + (Cf. COPYING): +native_client_sdk is licensed as follows + (Cf. LICENSE): +====================================================================== +Copyright 2010, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +====================================================================== +toolchain/linux_x86 is licensed as follows + (Cf. toolchain/linux_x86/COPYING): +toolchain/mac_x86 is licensed as follows + (Cf. toolchain/mac_x86/COPYING): +toolchain/win_x86 is licensed as follows + (Cf. toolchain/win_x86/COPYING): +toolchain/linux_x86_newlib is licensed as follows + (Cf. toolchain/linux_x86_newlib/COPYING): +toolchain/mac_x86_newlib is licensed as follows + (Cf. toolchain/mac_x86_newlib/COPYING): +toolchain/win_x86_newlib is licensed as follows + (Cf. toolchain/win_x86_newlib/COPYING): +====================================================================== + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + + + +====================================================================== +toolchain/linux_x86 is licensed as follows + (Cf. toolchain/linux_x86/COPYING.LIB): +toolchain/mac_x86 is licensed as follows + (Cf. toolchain/mac_x86/COPYING.LIB): +toolchain/win_x86 is licensed as follows + (Cf. toolchain/win_x86/COPYING.LIB): +toolchain/linux_x86_newlib is licensed as follows + (Cf. toolchain/linux_x86_newlib/COPYING.LIB): +toolchain/mac_x86_newlib is licensed as follows + (Cf. toolchain/mac_x86_newlib/COPYING.LIB): +toolchain/win_x86_newlib is licensed as follows + (Cf. toolchain/win_x86_newlib/COPYING.LIB): +====================================================================== + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + + +====================================================================== +toolchain/linux_x86 is licensed as follows + (Cf. toolchain/linux_x86/COPYING.RUNTIME): +toolchain/mac_x86 is licensed as follows + (Cf. toolchain/mac_x86/COPYING.RUNTIME): +toolchain/win_x86 is licensed as follows + (Cf. toolchain/win_x86/COPYING.RUNTIME): +toolchain/linux_x86_newlib is licensed as follows + (Cf. toolchain/linux_x86_newlib/COPYING.RUNTIME): +toolchain/mac_x86_newlib is licensed as follows + (Cf. toolchain/mac_x86_newlib/COPYING.RUNTIME): +toolchain/win_x86_newlib is licensed as follows + (Cf. toolchain/win_x86_newlib/COPYING.RUNTIME): +====================================================================== +GCC RUNTIME LIBRARY EXCEPTION + +Version 3.1, 31 March 2009 + +Copyright (C) 2009 Free Software Foundation, Inc. <http://fsf.org/> + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +This GCC Runtime Library Exception ("Exception") is an additional +permission under section 7 of the GNU General Public License, version +3 ("GPLv3"). It applies to a given file (the "Runtime Library") that +bears a notice placed by the copyright holder of the file stating that +the file is governed by GPLv3 along with this Exception. + +When you use GCC to compile a program, GCC may combine portions of +certain GCC header files and runtime libraries with the compiled +program. The purpose of this Exception is to allow compilation of +non-GPL (including proprietary) programs to use, in this way, the +header files and runtime libraries covered by this Exception. + +0. Definitions. + +A file is an "Independent Module" if it either requires the Runtime +Library for execution after a Compilation Process, or makes use of an +interface provided by the Runtime Library, but is not otherwise based +on the Runtime Library. + +"GCC" means a version of the GNU Compiler Collection, with or without +modifications, governed by version 3 (or a specified later version) of +the GNU General Public License (GPL) with the option of using any +subsequent versions published by the FSF. + +"GPL-compatible Software" is software whose conditions of propagation, +modification and use would permit combination with GCC in accord with +the license of GCC. + +"Target Code" refers to output from any compiler for a real or virtual +target processor architecture, in executable form or suitable for +input to an assembler, loader, linker and/or execution +phase. Notwithstanding that, Target Code does not include data in any +format that is used as a compiler intermediate representation, or used +for producing a compiler intermediate representation. + +The "Compilation Process" transforms code entirely represented in +non-intermediate languages designed for human-written code, and/or in +Java Virtual Machine byte code, into Target Code. Thus, for example, +use of source code generators and preprocessors need not be considered +part of the Compilation Process, since the Compilation Process can be +understood as starting with the output of the generators or +preprocessors. + +A Compilation Process is "Eligible" if it is done using GCC, alone or +with other GPL-compatible software, or if it is done without using any +work based on GCC. For example, using non-GPL-compatible Software to +optimize any GCC intermediate representations would not qualify as an +Eligible Compilation Process. + +1. Grant of Additional Permission. + +You have permission to propagate a work of Target Code formed by +combining the Runtime Library with Independent Modules, even if such +propagation would otherwise violate the terms of GPLv3, provided that +all Target Code was generated by Eligible Compilation Processes. You +may then convey such a combination under terms of your choice, +consistent with the licensing of the Independent Modules. + +2. No Weakening of GCC Copyleft. + +The availability of this Exception does not imply any general +presumption that third-party software is unaffected by the copyleft +requirements of the license of GCC. + + + +====================================================================== +toolchain/linux_x86 is licensed as follows + (Cf. toolchain/linux_x86/COPYING3): +toolchain/mac_x86 is licensed as follows + (Cf. toolchain/mac_x86/COPYING3): +toolchain/win_x86 is licensed as follows + (Cf. toolchain/win_x86/COPYING3): +toolchain/linux_x86_newlib is licensed as follows + (Cf. toolchain/linux_x86_newlib/COPYING3): +toolchain/mac_x86_newlib is licensed as follows + (Cf. toolchain/mac_x86_newlib/COPYING3): +toolchain/win_x86_newlib is licensed as follows + (Cf. toolchain/win_x86_newlib/COPYING3): +====================================================================== + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. + + + +====================================================================== +toolchain/linux_x86 is licensed as follows + (Cf. toolchain/linux_x86/COPYING3.LIB): +toolchain/mac_x86 is licensed as follows + (Cf. toolchain/mac_x86/COPYING3.LIB): +toolchain/win_x86 is licensed as follows + (Cf. toolchain/win_x86/COPYING3.LIB): +toolchain/linux_x86_newlib is licensed as follows + (Cf. toolchain/linux_x86_newlib/COPYING3.LIB): +toolchain/mac_x86_newlib is licensed as follows + (Cf. toolchain/mac_x86_newlib/COPYING3.LIB): +toolchain/win_x86_newlib is licensed as follows + (Cf. toolchain/win_x86_newlib/COPYING3.LIB): +====================================================================== + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + + + +====================================================================== +third_party/pymox is licensed as follows + (Cf. third_party/pymox/COPYING): +====================================================================== + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +====================================================================== +third_party/scons-2.0.1 is licensed as follows + (Cf. third_party/scons-2.0.1/LICENSE): +====================================================================== + +Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + The SCons Foundation + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +====================================================================== +third_party/gmock is licensed as follows + (Cf. third_party/gmock/COPYING) +====================================================================== + +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +====================================================================== +third_party/gmock/scripts/generator is licensed as follows + (Cf. third_party/gmock/scripts/generator/COPYING) +====================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2007] Neal Norwitz + Portions Copyright [2007] Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +====================================================================== +third_party/gtest is licensed as follows + (Cf. third_party/gtest/COPYING) +====================================================================== + +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +====================================================================== +toolchain/linux_x86/x86_64-nacl/include/boost/ is licensed as follows + (Cf. toolchain/linux_x86/x86_64-nacl/include/boost/LICENSE_1_0.txt): +toolchain/mac_x86/x86_64-nacl/include/boost/ is licensed as follows + (Cf. toolchain/mac_x86/x86_64-nacl/include/boost/LICENSE_1_0.txt): +toolchain/win_x86/x86_64-nacl/include/boost/ is licensed as follows + (Cf. toolchain/win_x86/x86_64-nacl/include/boost/LICENSE_1_0.txt): +toolchain/linux_x86_newlib/x86_64-nacl/include/boost/ is licensed as follows + (Cf. toolchain/linux_x86_newlib/x86_64-nacl/include/boost/LICENSE_1_0.txt): +toolchain/mac_x86_newlib/x86_64-nacl/include/boost/ is licensed as follows + (Cf. toolchain/mac_x86_newlib/x86_64-nacl/include/boost/LICENSE_1_0.txt): +toolchain/win_x86_newlib/x86_64-nacl/include/boost/ is licensed as follows + (Cf. toolchain/win_x86_newlib/x86_64-nacl/include/boost/LICENSE_1_0.txt): +====================================================================== + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/native_client_sdk/src/OWNERS b/native_client_sdk/src/OWNERS new file mode 100644 index 0000000..c76eba0 --- /dev/null +++ b/native_client_sdk/src/OWNERS @@ -0,0 +1,4 @@ +set noparent +bradnelson@chromium.org +noelallen@chromium.org +erikkay@chromium.org diff --git a/native_client_sdk/src/PRESUBMIT.py b/native_client_sdk/src/PRESUBMIT.py new file mode 100755 index 0000000..8e41521 --- /dev/null +++ b/native_client_sdk/src/PRESUBMIT.py @@ -0,0 +1,23 @@ +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Documentation on PRESUBMIT.py can be found at: +# http://www.chromium.org/developers/how-tos/depottools/presubmit-scripts + + +def CheckChangeOnUpload(input_api, output_api): + report = [] + affected_files = input_api.AffectedFiles(include_deletes=False) + report.extend(input_api.canned_checks.PanProjectChecks( + input_api, output_api, project_name='Native Client')) + return report + + +def CheckChangeOnCommit(input_api, output_api): + report = [] + report.extend(CheckChangeOnUpload(input_api, output_api)) + report.extend(input_api.canned_checks.CheckTreeIsOpen( + input_api, output_api, + json_url='http://naclsdk-status.appspot.com/current?format=json')) + return report diff --git a/native_client_sdk/src/README b/native_client_sdk/src/README new file mode 100644 index 0000000..8c32d1f --- /dev/null +++ b/native_client_sdk/src/README @@ -0,0 +1,24 @@ +Welcome to the Native Client SDK. + +Native Client Tools Bundle +Version: ${VERSION} +Revision: ${REVISION} +Build Date: ${DATE} + +Please refer to the online documentation here: + + http://code.google.com/chrome/nativeclient + +OTHER DEVELOPMENT + +If you want to contribute to the Native Client SDK itself, the code site is +here: + + http://code.google.com/p/nativeclient-sdk/ + + +KNOWN ISSUES + +Please refer to the online documentation here: + + http://code.google.com/chrome/nativeclient/docs/releasenotes.html diff --git a/native_client_sdk/src/build_tools/MkLink/MkLink.c b/native_client_sdk/src/build_tools/MkLink/MkLink.c new file mode 100644 index 0000000..a821af0 --- /dev/null +++ b/native_client_sdk/src/build_tools/MkLink/MkLink.c @@ -0,0 +1,412 @@ +/* + * Copyright 2008, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <windows.h> +#include <commctrl.h> +#include <nsis/pluginapi.h> +#include <winternl.h> + +enum { kLargeBuf = 1024, kSmallBuf = 256 } ; + +#if defined(_MSC_VER) +/* Ensure these are treated as functions and not inlined as intrinsics, or disable /Oi */ +#pragma warning(disable:4164) /* intrinsic function not declared */ +#pragma function(memcpy, memset, memcmp) +#endif + +HMODULE hNrDll = NULL; +NTSTATUS (NTAPI *fNtCreateFile) (PHANDLE FileHandle, + ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, + ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, + ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength); +NTSTATUS (NTAPI *fNtClose) (HANDLE Handle); + +HMODULE hKernel32 = NULL; +BOOL (WINAPI *fCreateHardLink) (TCHAR * linkFileName, + TCHAR * existingFileName, LPSECURITY_ATTRIBUTES lpSecurityAttributes); +BOOL (WINAPI *fCreateSymbolicLink) (TCHAR * linkFileName, + TCHAR * existingFileName, DWORD flags); + +#undef CreateHardLink +#undef CreateSymbolicLink +#ifdef UNICODE +#define CreateHardLink "CreateHardLinkW" +#define CreateSymbolicLink "CreateSymbolicLinkW" +#else +#define CreateHardLink "CreateHardLinkA" +#define CreateSymbolicLink "CreateSymbolicLinkA" +#endif + +BOOL MakeHardLink(TCHAR *linkFileName, TCHAR *existingFileName) { + if (!hKernel32) + hKernel32 = LoadLibrary(_T("KERNEL32.DLL")); + if (hKernel32) { + if (!fCreateHardLink) + fCreateHardLink = GetProcAddress(hKernel32, CreateHardLink); + if (fCreateHardLink) + return fCreateHardLink(linkFileName, existingFileName, NULL); + } + return FALSE; +} + +BOOL MakeSymLink(TCHAR *linkFileName, TCHAR *existingFileName, BOOL dirLink) { + TCHAR *f1 = HeapAlloc(GetProcessHeap(), 0, sizeof(TCHAR)*kLargeBuf); + TCHAR *f2 = HeapAlloc(GetProcessHeap(), 0, sizeof(TCHAR)*kLargeBuf); + SECURITY_ATTRIBUTES sec_attr = { sizeof (SECURITY_ATTRIBUTES), NULL, FALSE}; + OBJECT_ATTRIBUTES obj_attr; + IO_STATUS_BLOCK io_block; + TCHAR *p, *q; + HANDLE f; + BOOL status; + if (!f1 || !f2) + return FALSE; + lstrcpy(f1, linkFileName); + for (p = f1; p[0]; p++) + if (p[0] == _T('/')) + p[0] = _T('\\'); + q = f1; + while (q[0]) { + p = q; + do { + q++; + } while (q[0] && q[0] != '\\'); + } + if (p[0] = '\\') { + TCHAR c = p[1]; + p[1] = 0; + status = GetVolumeInformation(f1, NULL, 0, NULL, NULL, NULL, + f2, sizeof(f2)); + p[1] = c; + } else { + status = GetVolumeInformation(NULL, NULL, 0, NULL, NULL, NULL, + f2, sizeof(f2)); + } + /* If it's NFS then we can create real symbolic link. */ + if (!lstrcmpi(f2, _T("NFS"))) { + lstrcpy(f2, existingFileName); + for (p = f2; p[0]; p++) + if (p[0] == _T('\\')) + p[0] = _T('/'); + if (!hNrDll) + hNrDll = LoadLibrary(_T("NTDLL.DLL")); + if (hNrDll) { + if (!fNtCreateFile) + fNtCreateFile = GetProcAddress(hNrDll, "NtCreateFile"); + if (!fNtClose) + fNtClose = GetProcAddress(hNrDll, "NtClose"); + if (fNtCreateFile && fNtClose) { + struct { + ULONG offset; + UCHAR flags; + UCHAR nameLength; + USHORT valueLength; + CHAR name[21]; + /* To prevent troubles with alignment */ + CHAR value[kLargeBuf*sizeof(WCHAR)]; + } *ea_info = HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, + sizeof(*ea_info)); + WCHAR *fn = HeapAlloc(GetProcessHeap(), + 0, + sizeof(TCHAR)*kLargeBuf); + UNICODE_STRING n = { lstrlen(f1), kLargeBuf, fn }; + ea_info->nameLength = 20; + lstrcpy(ea_info->name, "NfsSymlinkTargetName"); +#ifdef UNICODE + lstrcpy(fn, f1); + lstrcpy((LPWSTR)ea_info->value, existingFileName); +#else + MultiByteToWideChar(CP_ACP, 0, f1, -1, fn, kLargeBuf); + MultiByteToWideChar(CP_ACP, 0, existingFileName, -1, + (LPWSTR)ea_info->value, + sizeof(ea_info->value)/sizeof(WCHAR)); +#endif + ea_info->valueLength = + lstrlenW((LPWSTR)ea_info->value)*sizeof(WCHAR); + InitializeObjectAttributes(&obj_attr, &n, OBJ_CASE_INSENSITIVE, + NULL, NULL); + status = fNtCreateFile( + &f, FILE_WRITE_DATA | FILE_WRITE_EA | SYNCHRONIZE, &obj_attr, + &io_block, NULL, FILE_ATTRIBUTE_SYSTEM, FILE_SHARE_READ | + FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, + FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT, + &ea_info, 1024 * sizeof (WCHAR)); + if (NT_SUCCESS(status)) { + fNtClose(f); + HeapFree(GetProcessHeap(), 0, fn); + HeapFree(GetProcessHeap(), 0, ea_info); + HeapFree(GetProcessHeap(), 0, f2); + HeapFree(GetProcessHeap(), 0, f1); + return TRUE; + } + HeapFree(GetProcessHeap(), 0, fn); + HeapFree(GetProcessHeap(), 0, ea_info); + } + } + } + lstrcpy(f2, existingFileName); + for (p = f2; p[0]; p++) + if (p[0] == _T('/')) + p[0] = _T('\\'); + if (!hKernel32) + hKernel32 = LoadLibrary(_T("KERNEL32.DLL")); + if (hKernel32) { + if (!fCreateSymbolicLink) + fCreateSymbolicLink = GetProcAddress(hKernel32, CreateSymbolicLink); + if (fCreateSymbolicLink) { + if (fCreateSymbolicLink(f1, f2, + dirLink ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0)) { + HeapFree(GetProcessHeap(), 0, f2); + HeapFree(GetProcessHeap(), 0, f1); + return TRUE; + } + } + } + if (dirLink) { + /* Ignore errors - file may already exist */ + CreateDirectory(f1, NULL); + f = CreateFile(f1, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, + NULL ); + if (f != INVALID_HANDLE_VALUE) { + struct rp_info { + DWORD tag; + DWORD dataLength; + WORD reserved1; + WORD targetLength; + WORD targetMaxLength; + WORD reserved2; + WCHAR target[kLargeBuf+4]; + } *rp_info = HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, + sizeof(*rp_info)); + DWORD len; + WCHAR *startlink, *endlink; + rp_info->tag = IO_REPARSE_TAG_MOUNT_POINT; + rp_info->target[0] = L'\\'; + rp_info->target[1] = L'?'; + rp_info->target[2] = L'?'; + rp_info->target[3] = L'\\'; + if (((f2[0] == _T('\\')) && (f2[1] == _T('\\'))) || + ((f2[1] == _T(':')) && (f2[2] == _T('\\')))) { +#ifdef UNICODE + lstrcpy(rp_info->target+4, f2); +#else + MultiByteToWideChar(CP_ACP, 0, f2, -1, + rp_info->target+4, kLargeBuf); +#endif + } else { +#ifdef UNICODE + GetFullPathNameW(f1, 1024, rp_info->target+4, &startlink); + lstrcpy(startlink, f2); +#else + MultiByteToWideChar(CP_ACP, 0, f1, -1, + (LPWSTR)f1, kLargeBuf/sizeof(WCHAR)); + GetFullPathNameW(f1, 1024, rp_info->target+4, &startlink); + MultiByteToWideChar(CP_ACP, 0, f2, -1, + startlink, kLargeBuf+4-(startlink-rp_info->target)); +#endif + } + /* Remove "XXX/../" and replace "/" with "\" */ + for (startlink = endlink = rp_info->target+4; + endlink[0]; startlink++, endlink++) { + startlink[0] = endlink[0]; + if ((startlink[0] == L'\\') && + (startlink[-1] == L'.') && + (startlink[-2] == L'.')) { + for (startlink--; startlink > rp_info->target+4 && + startlink[0] != L'\\'; startlink--) + { } + for (startlink--; startlink > rp_info->target+4 && + startlink[0] != L'\\'; startlink--) + { } + if (startlink < rp_info->target+4) + startlink = rp_info->target+4; + } + } + startlink[0] = endlink[0]; + rp_info->targetLength = lstrlenW(rp_info->target)*sizeof(WCHAR); + rp_info->targetMaxLength = rp_info->targetLength+sizeof(WCHAR); + rp_info->dataLength = rp_info->targetMaxLength + +FIELD_OFFSET(struct rp_info, target) + -FIELD_OFFSET(struct rp_info, reserved1) + +sizeof(WCHAR); + if (DeviceIoControl(f, 0x900A4, rp_info, + rp_info->dataLength + +FIELD_OFFSET(struct rp_info, reserved1), + NULL, 0, &len, NULL)) { + CloseHandle(f); + HeapFree(GetProcessHeap(), 0, rp_info); + HeapFree(GetProcessHeap(), 0, f2); + HeapFree(GetProcessHeap(), 0, f1); + return TRUE; + } + CloseHandle(f); + RemoveDirectory(f1); + HeapFree(GetProcessHeap(), 0, rp_info); + } + } + for (p = f2; p[0]; p++) + if (p[0] == _T('\\')) + p[0] = _T('/'); + f = CreateFile(f1, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_SYSTEM, NULL); + if (f != INVALID_HANDLE_VALUE) { + struct { + WCHAR sig[4]; + WCHAR value[kLargeBuf]; + } *link_info = HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, + sizeof(*link_info)); + DWORD towrite, written; + link_info->sig[0] = 0x6e49; + link_info->sig[1] = 0x7874; + link_info->sig[2] = 0x4e4c; + link_info->sig[3] = 0x014b; +#ifdef UNICODE + lstrcpy(link_info->value, f2); +#else + MultiByteToWideChar(CP_ACP, 0, f2, -1, link_info->value, kLargeBuf); +#endif + towrite = lstrlenW(link_info->value)*sizeof(WCHAR)+sizeof(link_info->sig); + WriteFile(f, link_info, towrite, &written, NULL); + CloseHandle(f); + if (written == towrite) { + HeapFree(GetProcessHeap(), 0, link_info); + HeapFree(GetProcessHeap(), 0, f2); + HeapFree(GetProcessHeap(), 0, f1); + return TRUE; + } + HeapFree(GetProcessHeap(), 0, link_info); + } + HeapFree(GetProcessHeap(), 0, f2); + HeapFree(GetProcessHeap(), 0, f1); + return FALSE; +} + +HINSTANCE instance; + +HWND parent, list; + +/* + * Show message in NSIS details window. + */ +void NSISprint(const TCHAR *str) { + if (list && *str) { + LVITEM item = { + /* mask */ LVIF_TEXT, + /* iItem */ SendMessage(list, LVM_GETITEMCOUNT, 0, 0), + /* iSubItem */ 0, /* state */ 0, /* stateMask */ 0, + /* pszText */ (TCHAR *) str, /* cchTextMax */ 0, + /* iImage */ 0, /* lParam */ 0, /* iIndent */ 0, + /* iGroupId */ 0, /* cColumns */ 0, /* puColumns */ NULL, + /* piColFmt */ NULL, /* iGroup */ 0}; + ListView_InsertItem(list, &item); + ListView_EnsureVisible(list, item.iItem, 0); + } +} + +enum linktype { HARDLINK, SOFTLINKD, SOFTLINKF }; + +void makelink(HWND hwndParent, int string_size, TCHAR *variables, + stack_t **stacktop, extra_parameters *extra, enum linktype type) { + TCHAR *msg = HeapAlloc(GetProcessHeap(), 0, sizeof(TCHAR)*kLargeBuf); + TCHAR *from = HeapAlloc(GetProcessHeap(), 0, sizeof(TCHAR)*kSmallBuf); + TCHAR *to = HeapAlloc(GetProcessHeap(), 0, sizeof(TCHAR)*kSmallBuf); + TCHAR *msgFormat = + type == HARDLINK ? _T("Link: \"%s\" to \"%s\"%s") : + type == SOFTLINKD ? _T("Symbolic Directory Link: \"%s\" to \"%s\"%s") : + _T("Symbolic Link: \"%s\" to \"%s\"%s"); + BOOL res; + parent = hwndParent; + list = FindWindowEx(FindWindowEx(parent, NULL, _T("#32770"), NULL), + NULL, _T("SysListView32"), NULL); + + EXDLL_INIT(); + + if (!msg || !from || !to) { + MessageBox(parent, _T("Fatal error: no memory for MkLink"), 0, MB_OK); + } + + if (popstringn(from, kSmallBuf)) { + MessageBox(parent, + _T("Usage: MkLink::Hard \"to_file\" \"from_file\" "), 0, MB_OK); + } + + if (popstringn(to, kSmallBuf)) { + MessageBox(parent, + _T("Usage: MkLink::Hard \"fo_file\" \"from_file\" "),0,MB_OK); + } + + switch (type) { + case HARDLINK: + res = MakeHardLink(from, to); + break; + case SOFTLINKD: + res = MakeSymLink(from, to, TRUE); + break; + case SOFTLINKF: + res = MakeSymLink(from, to, FALSE); + break; + } + wsprintf(msg, msgFormat, to, from, res ? _T("") : _T(" - fail...")); + NSISprint(msg); + + HeapFree(GetProcessHeap(), 0, to); + HeapFree(GetProcessHeap(), 0, from); + HeapFree(GetProcessHeap(), 0, msg); +} + +void __declspec(dllexport) Hard(HWND hwndParent, int string_size, + TCHAR *variables, stack_t **stacktop, + extra_parameters *extra) { + makelink(hwndParent, string_size, variables, stacktop, extra, HARDLINK); +} + +void __declspec(dllexport) SoftD(HWND hwndParent, int string_size, + TCHAR *variables, stack_t **stacktop, + extra_parameters *extra) { + makelink(hwndParent, string_size, variables, stacktop, extra, SOFTLINKD); +} + +void __declspec(dllexport) SoftF(HWND hwndParent, int string_size, + TCHAR *variables, stack_t **stacktop, + extra_parameters *extra) { + makelink(hwndParent, string_size, variables, stacktop, extra, SOFTLINKF); +} + +BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) { + instance = hInst; + return TRUE; +} diff --git a/native_client_sdk/src/build_tools/MkLink/MkLink.sln b/native_client_sdk/src/build_tools/MkLink/MkLink.sln new file mode 100644 index 0000000..05a60ff --- /dev/null +++ b/native_client_sdk/src/build_tools/MkLink/MkLink.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MkLink", "MkLink.vcproj", "{5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug Unicode|Win32 = Debug Unicode|Win32 + Debug|Win32 = Debug|Win32 + Release Unicode|Win32 = Release Unicode|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}.Debug Unicode|Win32.ActiveCfg = Debug Unicode|Win32 + {5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}.Debug Unicode|Win32.Build.0 = Debug Unicode|Win32 + {5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}.Debug|Win32.ActiveCfg = Debug|Win32 + {5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}.Debug|Win32.Build.0 = Debug|Win32 + {5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}.Release Unicode|Win32.ActiveCfg = Release Unicode|Win32 + {5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}.Release Unicode|Win32.Build.0 = Release Unicode|Win32 + {5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}.Release|Win32.ActiveCfg = Release|Win32 + {5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/native_client_sdk/src/build_tools/MkLink/MkLink.vcproj b/native_client_sdk/src/build_tools/MkLink/MkLink.vcproj new file mode 100644 index 0000000..0067ad6 --- /dev/null +++ b/native_client_sdk/src/build_tools/MkLink/MkLink.vcproj @@ -0,0 +1,484 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9.00" + Name="MkLink" + ProjectGUID="{5E3E2AFD-1D6B-4997-A9B5-8ECBC8F6C31A}" + RootNamespace="MkLink" + TargetFrameworkVersion="0" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Release|Win32" + OutputDirectory=".\Release" + IntermediateDirectory=".\Release" + ConfigurationType="2" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="false" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + PreprocessorDefinitions="NDEBUG" + MkTypLibCompatible="true" + SuppressStartupBanner="true" + TargetEnvironment="1" + TypeLibraryName=".\Release/MkLink.tlb" + HeaderFileName="" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="1" + InlineFunctionExpansion="0" + AdditionalIncludeDirectories="." + PreprocessorDefinitions="NDEBUG;WIN32;_WINDOWS;_USRDLL;EXDLL_EXPORTS;NSISCALL=__stdcall" + StringPooling="true" + RuntimeLibrary="2" + BufferSecurityCheck="false" + EnableFunctionLevelLinking="true" + PrecompiledHeaderFile=".\Release/MkLink.pch" + AssemblerListingLocation=".\Release/" + ObjectFile=".\Release/" + ProgramDataBaseFileName=".\Release/" + WarningLevel="3" + SuppressStartupBanner="true" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="NDEBUG" + Culture="1033" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="odbc32.lib odbccp32.lib nsis\pluginapi.lib" + OutputFile=".\Release/MkLink.dll" + LinkIncremental="1" + SuppressStartupBanner="true" + GenerateManifest="false" + IgnoreAllDefaultLibraries="true" + IgnoreDefaultLibraryNames="libc.lib" + ProgramDatabaseFile=".\Release/MkLink.pdb" + OptimizeForWindows98="1" + EntryPointSymbol="DllMain" + RandomizedBaseAddress="1" + DataExecutionPrevention="0" + ImportLibrary=".\Release/MkLink.lib" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + SuppressStartupBanner="true" + OutputFile=".\Release/MkLink.bsc" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Debug|Win32" + OutputDirectory=".\Debug" + IntermediateDirectory=".\Debug" + ConfigurationType="2" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="false" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + PreprocessorDefinitions="_DEBUG" + MkTypLibCompatible="true" + SuppressStartupBanner="true" + TargetEnvironment="1" + TypeLibraryName=".\Debug/MkLink.tlb" + HeaderFileName="" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="." + PreprocessorDefinitions="_DEBUG;WIN32;_WINDOWS;_USRDLL;EXDLL_EXPORTS;NSISCALL=__stdcall" + MinimalRebuild="true" + BasicRuntimeChecks="0" + RuntimeLibrary="3" + BufferSecurityCheck="false" + PrecompiledHeaderFile=".\Debug/MkLink.pch" + AssemblerListingLocation=".\Debug/" + ObjectFile=".\Debug/" + ProgramDataBaseFileName=".\Debug/" + WarningLevel="3" + SuppressStartupBanner="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="_DEBUG" + Culture="1033" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="odbc32.lib odbccp32.lib nsis\pluginapi.lib" + OutputFile=".\Debug/MkLink.dll" + LinkIncremental="2" + SuppressStartupBanner="true" + GenerateManifest="false" + IgnoreAllDefaultLibraries="true" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="true" + ProgramDatabaseFile=".\Debug/MkLink.pdb" + EntryPointSymbol="DllMain" + RandomizedBaseAddress="1" + DataExecutionPrevention="0" + ImportLibrary=".\Debug/MkLink.lib" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + SuppressStartupBanner="true" + OutputFile=".\Debug/MkLink.bsc" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release Unicode|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="2" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="false" + CharacterSet="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + PreprocessorDefinitions="NDEBUG" + MkTypLibCompatible="true" + SuppressStartupBanner="true" + TargetEnvironment="1" + TypeLibraryName=".\Release Unicode/MkLink.tlb" + HeaderFileName="" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="1" + InlineFunctionExpansion="0" + AdditionalIncludeDirectories="." + PreprocessorDefinitions="NDEBUG;WIN32;_WINDOWS;_USRDLL;EXDLL_EXPORTS;NSISCALL=__stdcall" + StringPooling="true" + RuntimeLibrary="2" + BufferSecurityCheck="false" + EnableFunctionLevelLinking="true" + PrecompiledHeaderFile=".\Release Unicode/MkLink.pch" + AssemblerListingLocation=".\Release Unicode/" + ObjectFile=".\Release Unicode/" + ProgramDataBaseFileName=".\Release Unicode/" + WarningLevel="3" + SuppressStartupBanner="true" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="NDEBUG" + Culture="1033" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="odbc32.lib odbccp32.lib nsis\pluginapi.lib" + OutputFile=".\Release Unicode/MkLink.dll" + LinkIncremental="1" + SuppressStartupBanner="true" + GenerateManifest="false" + IgnoreAllDefaultLibraries="true" + IgnoreDefaultLibraryNames="libc.lib" + ProgramDatabaseFile=".\Release Unicode/MkLink.pdb" + OptimizeForWindows98="1" + EntryPointSymbol="DllMain" + RandomizedBaseAddress="1" + DataExecutionPrevention="0" + ImportLibrary=".\Release Unicode/MkLink.lib" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + SuppressStartupBanner="true" + OutputFile=".\Release Unicode/MkLink.bsc" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Debug Unicode|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="2" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="false" + CharacterSet="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + PreprocessorDefinitions="_DEBUG" + MkTypLibCompatible="true" + SuppressStartupBanner="true" + TargetEnvironment="1" + TypeLibraryName=".\Debug Unicode/MkLink.tlb" + HeaderFileName="" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="." + PreprocessorDefinitions="_DEBUG;WIN32;_WINDOWS;_USRDLL;EXDLL_EXPORTS;NSISCALL=__stdcall" + MinimalRebuild="true" + BasicRuntimeChecks="0" + RuntimeLibrary="3" + BufferSecurityCheck="false" + PrecompiledHeaderFile=".\Debug Unicode/MkLink.pch" + AssemblerListingLocation=".\Debug Unicode/" + ObjectFile=".\Debug Unicode/" + ProgramDataBaseFileName=".\Debug Unicode/" + WarningLevel="3" + SuppressStartupBanner="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="_DEBUG" + Culture="1033" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="odbc32.lib odbccp32.lib nsis\pluginapi.lib" + OutputFile=".\Debug Unicode/MkLink.dll" + LinkIncremental="2" + SuppressStartupBanner="true" + GenerateManifest="false" + IgnoreAllDefaultLibraries="true" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="true" + ProgramDatabaseFile=".\Debug Unicode/MkLink.pdb" + EntryPointSymbol="DllMain" + RandomizedBaseAddress="1" + DataExecutionPrevention="0" + ImportLibrary=".\Debug Unicode/MkLink.lib" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + SuppressStartupBanner="true" + OutputFile=".\Debug Unicode/MkLink.bsc" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" + > + <File + RelativePath=".\MkLink.c" + > + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="" + PreprocessorDefinitions="" + /> + </FileConfiguration> + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="" + PreprocessorDefinitions="" + /> + </FileConfiguration> + <FileConfiguration + Name="Debug Unicode|Win32" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="" + PreprocessorDefinitions="" + /> + </FileConfiguration> + <FileConfiguration + Name="Release Unicode|Win32" + > + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="" + PreprocessorDefinitions="" + /> + </FileConfiguration> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl" + > + <File + RelativePath="nsis\pluginapi.h" + > + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" + > + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/native_client_sdk/src/build_tools/MkLink/Release Unicode/MkLink.dll b/native_client_sdk/src/build_tools/MkLink/Release Unicode/MkLink.dll Binary files differnew file mode 100644 index 0000000..c4c604d --- /dev/null +++ b/native_client_sdk/src/build_tools/MkLink/Release Unicode/MkLink.dll diff --git a/native_client_sdk/src/build_tools/README_NSIS.txt b/native_client_sdk/src/build_tools/README_NSIS.txt new file mode 100644 index 0000000..e51141a --- /dev/null +++ b/native_client_sdk/src/build_tools/README_NSIS.txt @@ -0,0 +1,59 @@ +Note: This whole process takes about 1:30 to 2 hours for someone who is pretty +familiar with it. If this is your first attempt, it's best to plan for this to +take the whole day. + +Step 0: Make sure your toolchain dir is clean. + The easiest way to do this is to remove these dirs and then do a gclient sync: + rmdir src\toolchain + rmdir src\third_party + scons -c examples + gclient sync + Or: start with a fresh checkout from nothing. + +Step 1: Generate a base copy of make_native_client_sdk.nsi + Edit generate_windows_installer.py so that it runs everything up to + make_native_client_sdk2.sh. + Easiest: add a 'break' after waiting for make_native_client_sdk.sh subprocess. + This is around line 194. + In src, python build_tools\generate_installers.py --development + This will produce build_tools\make_native_client_sdk.nsi + +Step 3: Create a back-up and an editable copy of make_native_client_sdk.nsi + Make 2 copies of make_native_client_sdk.nsi: + copy make_native_client_sdk.nsi make_native_client_sdk-orig.nsi + copy make_native_client_sdk.nsi make_native_client_sdk-new.nsi + +Step 4: Edit the .nsi file to contain the appropriate changes. + Edit the "-new" version of make_native_client_sdk. + Manually apply the patch in nsi_patch.txt (and any other necessary edits) to + make_native_client_sdk-new.nsi + +Step 5: Create the new patch file. + cd build_tools + copy nsi_patch.txt nsi_patch-old.txt + ..\third_party\cygwin\bin\diff -Naur make_native_client_sdk-orig.nsi make_native_client_sdk-new.nsi > nsi_patch.txt + Note: the order of -orig and -new is important. + +Step 6: Edit the patch file. + Edit nsi_patch.txt + Change every occurrence of native_client_sdk_0_x_y to %NACL_SDK_FULL_NAME% + Change every occurrence of third_party\cygwin\ to %$CYGWIN_DIR% + Note: the trailing '\' on third_party\cygwin_dir\ is important. + At the top of the file, change the diff file names to both be + make_native_client_sdk.nsi + +Step 7: Test the patch. + (In the build_tools dir): + copy make_native_client_sdk-orig.nsi make_native_client_sdk.nsi + ..\third_party\cygwin\bin\bash make_native_client_sdk2.sh -V 0.x.y -v -n + Note: |x| and |y| have to match the original values in + native_client_sdk_0_x_y from Step 6. + Note: This step can take a long time (5 min or more). + +Step 8: Test the installer. + Step 7 should have produced nacl_sdk.exe in the root of the nacl-sdk project. + Run this from the Finder, and install the SDK to see if it works. + +Step 9: Upload the changes. + If everything works, make a CL. + REMEMBER: remove the 'break' statement you added in Step 1. diff --git a/native_client_sdk/src/build_tools/__init__.py b/native_client_sdk/src/build_tools/__init__.py new file mode 100644 index 0000000..c324a56 --- /dev/null +++ b/native_client_sdk/src/build_tools/__init__.py @@ -0,0 +1,10 @@ +#! -*- python -*- + +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""build_tools Package + +This package contains general python utilities that are used for +creating the Native Client SDK.""" diff --git a/native_client_sdk/src/build_tools/apply_patch.py b/native_client_sdk/src/build_tools/apply_patch.py new file mode 100755 index 0000000..bc1f126 --- /dev/null +++ b/native_client_sdk/src/build_tools/apply_patch.py @@ -0,0 +1,534 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" Utility for applying svn-diff-style patches to files. + +This file contains a collection of classes for parsing svn-diff patch files +and applying these patches to files in a directory. The main API class is +ApplyPatch. It has two entry point: method Parse to parse a patch file and +method Apply to apply that patch to a base directory. + +Most other classes are internal only. Each class corresponds to a structure in +the patch file, such as _Range, which corresponds to the range line identified +by the heading and traling '@@'. +""" + +__author__ = 'gwink@google.com (Georges Winkenbach)' + +import os +import re +import shutil +import sys +import tempfile + +# Constants: regular expressions for parsing patch lines. +RE_HEADER_ORIGINAL_FILE = re.compile('---\s+([^\s]+)') +RE_HEADER_OUT_FILE = re.compile('\+\+\+\s+([^\s]+)') +RE_RANGE = re.compile('@@\s*-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s*@@') + +# Constants: type of diff lines. +CONTEXTUAL_DIFF_LINE = 0 +ADDED_DIFF_LINE = 1 +DELETED_DIFF_LINE = 2 +NOT_A_DIFF_LINE = 3 + + +class Error(Exception): + ''' Exceptions raised in this file. + + Attributes: + error: a string that describes the error. + context: a string that shows the context where the error occured. + ''' + + def __init__(self, context='', error='error'): + ''' Init Error with context and error info. ''' + super(Error, self).__init__(context, error) + self.error = error + self.context = context + + def __str__(self): + out = 'Error %s.' % self._error + return out + + +class _Range(object): + ''' Corresponds to a range info in the patch file. + + _Range encapsulates the source- and destination-file range information + found in the patch file. + + Attributes: + src_start_line: index to the first line in the source file. + src_line_count: the number of lines in the source file. + dest_start_line: index to the first line in the destination file. + dest_line_count: the number of lines in the destination file. + ''' + def __init__(self): + ''' Init _Range with empty source and destination range info. ''' + self.src_start_line = 0 + self.src_line_count = 0 + self.dest_start_line = 0 + self.dest_line_count = 0 + + def Parse(self, diff_lines): + ''' Parse a range diff line. + + Parse the line at index 0 in diff_lines as a range-info line and set the + range info accordingly. Raise an Error exception if the line is not a valid + range line. If successful, the line at index 0 is removed from diff_lines. + + Arguments: + diff_lines: a list of diff lines read from the patch file. + ''' + match = re.match(RE_RANGE, diff_lines[0]) + if not match: + raise Error(context=diff_lines[0], error='Bad range info') + + # Range in source file. The line count is optional and defaults to 1. + self.src_start_line = int(match.group(1)) + self.src_line_count = 1 + if (match.group(2)): + self.src_line_count = int(match.group(2)) + # Range in destination file. The line count is optional and defaults to 1. + self.dest_start_line = int(match.group(3)) + self.dest_line_count = 1 + if (match.group(4)): + self.dest_line_count = int(match.group(4)) + + diff_lines.pop(0) + + +class _ChangeHunk(object): + ''' A ChangeHunk is a range followed by a series of diff lines in the patch. + + Encapsulate the info represented by a range line and the subsequent diff + lines in the patch file. + + Attributes: + range: the range info, an instance of _Range + lines: a list containing the diff lines that follow the range info in + the patch file. + ''' + + def __init__(self): + ''' Init an empty _ChangeHunk. ''' + self.range = None + self.lines = [] + + @staticmethod + def _DiffLineType(line): + ''' Helper function that determines the type of a diff line. + + Arguments: + line: the diff line whose type must be determined. + Return: + One of CONTEXTUAL_DIFF_LINE, DELETED_DIFF_LINE, ADDED_DIFF_LINE or + NOT_A_DIFF_LINE. + ''' + # A contextual line starts with a space or can be an empty line. + if line[0] in [' ', '\n', '\r']: + return CONTEXTUAL_DIFF_LINE + # A delete line starts with '-'; but '---' denotes a header line. + elif line[0] == '-' and not line.startswith('---'): + return DELETED_DIFF_LINE + # A add line starts with '+'; but '+++' denotes a header line. + elif line[0] == '+' and not line.startswith('+++'): + return ADDED_DIFF_LINE + # Anything else is not a diff line. + else: + return NOT_A_DIFF_LINE + + def _ParseLines(self, diff_lines): + ''' Parse diff lines for a change-hunk. + + Parse the diff lines, update the change-hunk info accordingly and validate + the hunk info. The first line in diff_lines should be the line that follows + the range info in the patch file.. The subsequent lines should be diff + lines. Raise an Error exception if the hunk info doesn't validate. Matched + lines are removed from diff_lines. + + Arguments: + diff_lines: a list of diff lines read from the patch file. + ''' + src_line_count = 0 + dest_line_count = 0 + index = 0 + for index, line in enumerate(diff_lines): + line_type = self._DiffLineType(line) + if line_type == CONTEXTUAL_DIFF_LINE: + # Contextual line (maybe an empty line). + src_line_count += 1 + dest_line_count += 1 + elif line_type == DELETED_DIFF_LINE: + # Deleted line. + src_line_count += 1 + elif line_type == ADDED_DIFF_LINE: + # Added line. + dest_line_count += 1 + else: + # Done. + break; + + # Store each valid diff line into the local list. + self.lines.append(line) + + # Remove lines that have been consumed from list. + del diff_lines[0:index] + + # Validate the change hunk. + if (src_line_count != self.range.src_line_count) or (dest_line_count != + self.range.dest_line_count): + raise Error(context=self.lines[0], + error='Range info doesn\'t match lines that follow') + + def Parse(self, diff_lines): + ''' Parse a change-hunk. + + Parse the range and subsequent diff lines for a change hunk. The first line + in diff_lines should be a range line starting with @@. Raise an Error + exception if the hunk info doesn't validate. Matched lines are removed from + diff_lines. + + Arguments: + diff_lines: a list of diff lines read from the patch file. + ''' + self.range = _Range() + self.range.Parse(diff_lines) + self._ParseLines(diff_lines) + + def Apply(self, line_num, in_file, out_file): + ''' Apply the change hunk to in_file. + + Apply the change hunk to in_file, outputing the result to out_file. Raise a + Error exception if any inconsistency is detected. + + Arguments: + line_num: the last line number read from in_file, or 0 if the file has not + yet been read from. + in_file: the input (source) file. Maybe None if the hunk only adds + lines to a new file. + out_file: the output (destination) file. + Return: + the line number of the last line read (consumed) from in_file. + ''' + # If range starts at N, consume all lines from line_num to N-1. Don't read + # line N yet; what we'll do with it depend on the first diff line. Each + # line is written to the output file. + while line_num < self.range.src_start_line - 1: + out_file.write(in_file.readline()) + line_num = line_num + 1 + + for diff_line in self.lines: + diff_line_type = self._DiffLineType(diff_line) + if diff_line_type == CONTEXTUAL_DIFF_LINE: + # Remove the contextual-line marker, if present. + if diff_line[0] == ' ': + diff_line = diff_line[1:] + # Contextual line should match the input line. + line = in_file.readline() + line_num = line_num + 1 + if not line == diff_line: + raise Error(context=line, error='Contextual line mismatched') + out_file.write(line) + elif diff_line_type == DELETED_DIFF_LINE: + # Deleted line: consume it from in_file, verify that it matches the + # diff line (with marker removed), do not write it to out_file. + line = in_file.readline() + line_num = line_num + 1 + if not line == diff_line[1:]: + raise Error(context=line, error='Deleted line mismatched') + elif diff_line_type == ADDED_DIFF_LINE: + # Added line: simply write it to out_file, without the add-line marker. + diff_line = diff_line[1:] + out_file.write(diff_line) + else: + # Not a valid diff line; this is an internal error. + raise Error(context=diff_line, error='Unexpected diff line') + + return line_num + + +class _PatchHeader(object): + ''' The header portion of a patch. + + Encapsulates a header, the two lines prefixed with '---' and '+++' before + a series of change hunks. + + Attributes: + in_file_name: the file name for the input (source) file. + out_file_name: the file name for the output (new) file. + ''' + def __init__(self): + ''' Init _PatchHeader with no filenames. ''' + self.in_file_name = None + self.out_file_name = None + + def Parse(self, diff_lines): + ''' Parse header info. + + Parse the header information, the two lines starting with '---' and '+++' + respectively. Gather the in (source) file name and out (new) file name + locally. The first line in diff_lines should be a header line starting + with '---'. Raise an Error exception if no header info is found. Matched + lines are consumed (remove from diff_lines). + + Arguments: + diff_lines: a list of diff lines read from the patch. + ''' + match = self._MatchHeader(RE_HEADER_ORIGINAL_FILE, diff_lines) + self.in_file_name = match.group(1) + match = self._MatchHeader(RE_HEADER_OUT_FILE, diff_lines) + self.out_file_name = match.group(1) + + def _MatchHeader(self, header_re, diff_lines): + ''' Helper function to match a single header line using regular expression + header_re. + ''' + match = re.match(header_re, diff_lines[0]) + if not match: + raise Error(context=diff_lines[0], error='Bad header') + diff_lines.pop(0) + return match + + +class _Patch(object): + ''' A _Patch is the header and all the change hunks pertaining to a single + file. + + Encapsulates all the information for a single-file patch, including the + header and all the subsequent change hunks found in the patch file. + + Attributes: + header: the header info, an instance of _PatchHeader. + hunks: a list of _ChangeHunks. + ''' + def __init__(self): + ''' Init an empty _Patch. ''' + self.header = None + self.hunks = [] + + def _SrcFileIsOptional(self): + ''' Helper function that returns true if it's ok for this patch to not have + source file. (It's ok to not have a source file if the patch has a + single hunk and that hunk only adds lines to the destination file. + ''' + return len(self.hunks) == 1 and self.hunks[0].range.src_line_count == 0 + + def Parse(self, diff_lines): + ''' Parse a complete single-file patch. + + Parse a complete patch for a single file, updating the local patch info + accordingly. Raise an Error exception if any inconsistency is detected. + Patch lines from diff_lines are consumed as they are matched. The first line + in diff_lines should be a header line starting with '---'. + + Arguments: + diff_lines: a list diff lines read from the patch file. + ''' + # Parse the patch header + self.header = _PatchHeader() + self.header.Parse(diff_lines) + # The next line must be a range. + if not diff_lines[0].startswith('@@'): + raise Error(context=diff_lines[0], error='Bad range info') + + # Parse the range. + while diff_lines: + if diff_lines[0].startswith('@@'): + hunk = _ChangeHunk() + hunk.Parse(diff_lines) + self.hunks.append(hunk) + else: + break + + def Apply(self, base_path): + ''' Apply the patch to the directory base_path. + + Apply the single-file patch using base_path as the root directory. + The source file name, extracted from the header, is appended to base_path + to derive the full path to the file to be patched. The file is patched + in-place. That is, if the patching is successful, the source file path + will conatin the patched file when this method exits. + + Argument: + base_path: path to the root directory. + ''' + # Form an absolute path to the source file and verify that it exists if + # it is required. + in_path = os.path.abspath( + os.path.join(base_path, self.header.in_file_name)) + if not os.path.exists(in_path) and not self._SrcFileIsOptional(): + raise Error(context=in_path, error='Original file doesn\'t exists') + + in_file = None + out_file = None + temp_out_path = None + try: + # Patch the input file into a temporary output file. + if os.path.exists(in_path): + in_file = open(in_path) + with tempfile.NamedTemporaryFile(delete=False) as out_file: + line_num = 0 + # Apply all the change hunks. + for hunk in self.hunks: + line_num = hunk.Apply(line_num, in_file, out_file) + + # We ran out of change hunks, but there may be lines left in the input + # file. Copy them to the output file. + if in_file: + for line in in_file: + out_file.write(line) + + # Close the input and output files; retain the temporary output file + # path so we can rename it later. + temp_out_path = out_file.name + out_file.close() + out_file = None + if in_file: + in_file.close() + in_file = None + + # Rename the output file to the original input file name. Windows + # forces us to delete the original file first. Linux is fussy about + # renaming a temp file. So we copy and delete instead. + if os.path.exists(in_path): + os.remove(in_path) + shutil.copy(temp_out_path, in_path) + os.remove(temp_out_path) + except: + raise + finally: + if in_file: in_file.close() + if out_file: out_file.close() + if temp_out_path and os.path.exists(temp_out_path): + os.remove(temp_out_path) + + +class _PatchSet(object): + ''' All the patches from a patch file. + + Encapsulates all the patches found in a patch file. + ''' + def __init__(self): + self._patches = [] + + def Parse(self, patch_file): + ''' Parse all the patches in a patch file. + + Parse all the patches from a given patch file and store each patch info + into list _patches. Raise an Error exception if an inconsistency is found. + + Argument: + patch_file: a file descriptor for the patch file, ready for reading. + ''' + diff_lines = patch_file.readlines() + while diff_lines: + # Look for the start of a header. + if diff_lines[0].startswith('---'): + patch = _Patch() + patch.Parse(diff_lines) + self._patches.append(patch) + else: + # Skip unrecognized lines. + diff_lines.pop(0) + + def Apply(self, base_path): + ''' Apply all the patches. + + Apply all the patches to files using base_path as the root directory. + Raise an Error exception if an inconsistency is found. Files are patched + in-place. + + Argument: + base_path: path to the root directory in which to apply the patches. + ''' + for patch in self._patches: + patch.Apply(base_path) + + +class ApplyPatch(object): + ''' Read a patch fileand apply it to a given base directory. + + ApplyPatch provides support for reading and parsing a patch file and + applying the patches to files relative to a given base directory. + ''' + + def __init__(self): + ''' Init ApplyPatch to empty. ''' + self._patch_set = None + + def Parse(self, patch_path): + ''' Parse a patch file. + + Parse the patch file at path patch_path and store the patch info into + _patch_set. Parsing-error messages are sent to stderr. + + Argument: + patch_path: path to the patch file. + + Return: + True if the patch file is successfully read and ready to apply and False + otherwise. + ''' + try: + if not os.path.exists(patch_path): + raise Error(context=patch_path, error="Patch file not found"); + with open(patch_path) as patch_file: + self._patch_set = _PatchSet() + self._patch_set.Parse(patch_file) + + except Error as (error): + sys.stderr.write('Patch error: %s.\n' % error.error) + sys.stderr.write('At line: %s\n' % error.context) + return False + + return True + + def Apply(self, base_path): + ''' Apply a patch to files. + + Apply an entire set of patches to files using base_path as the base + directory. The files are patched in place. Patching-error message are output + to stderr. + + Argument: + base_path: path to the base directory in which to apply the patches. + + Return: + True if all the patches are applied successfully and False otherwise. + ''' + try: + if not os.path.exists(base_path): + raise Error(context=base_path, error="Invalid base path"); + if not os.path.isdir(base_path): + raise Error(context=base_path, error="Base path is not a directory"); + self._patch_set.Apply(base_path) + + except Error as (error): + sys.stderr.write('Processing error: %s.\n' % error.error) + sys.stderr.write('>>> %s\n' % error.context) + return False + + return True + + +def main(argv): + apply_patch = ApplyPatch() + if apply_patch.Parse(os.path.abspath(argv[0])): + if apply_patch.Apply(os.path.abspath(argv[1])): + sys.exit(0) + + sys.exit(1) + + +if __name__ == '__main__': + if len(sys.argv) != 3: + sys.stderr.write('Usage: %s patch-file base-dir\n' % + os.path.basename(sys.argv[0])) + sys.exit(1) + else: + main(sys.argv[1:]) + diff --git a/native_client_sdk/src/build_tools/build.scons b/native_client_sdk/src/build_tools/build.scons new file mode 100644 index 0000000..16d86f1 --- /dev/null +++ b/native_client_sdk/src/build_tools/build.scons @@ -0,0 +1,289 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Build file for running scripts in the build_tools directory + +Adapted from scons documentation: http://www.scons.org/wiki/UnitTests +and from ../project_templates/test.scons +""" + +__author__ = 'mball@google.com (Matt Ball)' + +from build_tools import build_utils +from build_tools import make_nacl_tools +from build_tools import make_sdk_tools +import os +import sys + +Import('env') + +# Add the system path to this environment so that we can run gclient and such +env.AppendENVPath('PATH', os.environ['PATH']) +# Duplicate all the shell environment variables, so that various Visual Studio +# tools will have the variables they expect (e.g. VS 2008 needs VS90COMNTOOLS). +# Do this using "if not in" instead of update(), so that os.environ variables +# don't override variables in env['ENV']. +for shell_var in os.environ: + if shell_var not in env['ENV']: + env['ENV'][shell_var] = os.environ[shell_var] + + +def run_generate_installer(env, target, source): + '''Runs the generate_installer script to create the SDK installer''' + args = [] + if env['IS_WINDOWS']: + from build_tools import generate_windows_installer + return generate_windows_installer.main(args) + else: + from build_tools import generate_installers + return generate_installers.main(args) + +installer_node = env.Command( + target='generate_installers_dummy_target.txt', + source='generate_installers.py', + action=run_generate_installer) + +env.Depends(installer_node, env.GetToolchainNode()) +env.Depends(installer_node, env.GetInstallerPrereqsNode()) +env.AddNodeAliases(installer_node, ['bot'], 'installer') + +install_file = 'nacl-sdk.exe' if sys.platform == 'win32' else 'nacl-sdk.tgz' +installer = os.path.join(os.path.dirname(env['ROOT_DIR']), install_file) + +def BuildSdkTools(env, target, source): + make_sdk_tools.MakeSdkTools(str(target[0].abspath), str(target[1].abspath)) + +sdk_tools_node = env.Command( + target=['nacl_sdk.zip', 'sdk_tools.tgz'], + source=[os.path.join('sdk_tools', 'sdk_update.py'), + 'make_sdk_tools.py', + os.path.join(env['ROOT_DIR'], 'LICENSE')], + action=BuildSdkTools) + +env.AddNodeAliases(sdk_tools_node, ['bot'], 'sdk_tools') + +installer_test_node = env.CreatePythonUnitTest( + 'tests/installer_test.py', + [installer_node if not env['USE_EXISTING_INSTALLER'] else installer], + buffered=False, + params=['--jobs=%s' % env['JOB_COUNT'], + '--outdir=%s' % os.path.join('scons-out', 'sdk_installer'), + '--nacl-sdk', env.File('nacl_sdk.zip'), + '--sdk-tools', env.File('sdk_tools.tgz'), + installer], + banner='test installer',) + +env.AddNodeToTestSuite(installer_test_node, ['bot'], + 'run_installer_test', 'large') + +env.Depends(env.GetInstallerTestNode(), installer_test_node) + +#------------------------------------------------------------------------------ +# Add build_tools unit tests + +def AddPythonUnitTest(command, file, size='small', **kwargs): + ''' Adds a new python unit test + + Args: + command: The scons command to run this unit test + file: The python file containing the unit test + size: How big is this test? small, medium, or large + kwargs: See named parameters to CreatePythonUnitTest + + returns: + test node for this newly created test''' + test_node = env.CreatePythonUnitTest(file, **kwargs) + env.AddNodeToTestSuite(test_node, ['bot'], command, size) + return test_node + +AddPythonUnitTest('run_build_utils_test', 'tests/build_utils_test.py') +AddPythonUnitTest('run_tar_archive_test', 'tests/tar_archive_test.py') +AddPythonUnitTest('run_update_manifest_test', + 'tests/update_manifest_test.py', size='medium') +AddPythonUnitTest('run_sdk_update_test', 'tests/sdk_update_test.py') +AddPythonUnitTest('run_set_nacl_env_test', 'tests/set_nacl_env_test.py', + dependencies=[env.GetToolchainNode()]) +AddPythonUnitTest('run_installer_contents_test', + 'tests/installer_contents_test.py') +AddPythonUnitTest('run_nacl_utils_test', 'nacl_sdk_scons/nacl_utils_test.py') +AddPythonUnitTest('run_path_set_test', 'tests/path_set_test.py') +AddPythonUnitTest('run_apply_patch_test', 'tests/apply_patch_test.py') +AddPythonUnitTest('run_nmf_test', + 'nacl_sdk_scons/nmf_test.py', + size='medium', + dependencies=[env.GetToolchainNode()], + params=['--toolchain-dir=%s' % + env['NACL_TOOLCHAIN_ROOTS'][('x86', 'glibc')]]) +AddPythonUnitTest('run_html_check_test', + 'html_checker.py', + params=[os.path.join(env['ROOT_DIR'], 'examples', name) + for name in ['index.html', 'index_staging.html']]) + +if env['IS_WINDOWS']: + AddPythonUnitTest('run_install_nsis_test', 'tests/install_nsis_test.py') + AddPythonUnitTest('run_nsis_script_test', 'tests/nsis_script_test.py') + + +#------------------------------------------------------------------------------ +# Put together the toolchain + +import gyp_extract + +ppapi_base = os.path.join(env['ROOT_DIR'], + 'third_party', + 'native_client', + 'ppapi') + +# Load ppapi_cpp.gypi +ppapi_cpp_gypi = gyp_extract.LoadGypFile(os.path.join(ppapi_base, + 'ppapi_cpp.gypi')) + +# Load ppapi_gl.gypi +ppapi_gl_gypi = gyp_extract.LoadGypFile(os.path.join(ppapi_base, + 'ppapi_gl.gypi')) + +# From ppapi_cpp.gypi:ppapi_c:c/[^/]*\.h +c_headers = gyp_extract.GypTargetSources( + ppapi_cpp_gypi, 'ppapi_c', 'c/[^/]*\.h') + +# From ppapi_cpp.gypi:ppapi_c:c/dev/[^/]*\.h +c_dev_headers = gyp_extract.GypTargetSources( + ppapi_cpp_gypi, 'ppapi_c', 'c/dev/[^/]*\.h') + +# From ppapi_cpp.gypi:ppapi_cpp_objects:cpp/[^/]*\.h +# From ppapi_cpp.gypi:ppapi_cpp:cpp/[^/]*\.h +cpp_headers = ( + gyp_extract.GypTargetSources( + ppapi_cpp_gypi, 'ppapi_cpp_objects', 'cpp/[^/]*\.h') + + gyp_extract.GypTargetSources( + ppapi_cpp_gypi, 'ppapi_cpp', 'cpp/[^/]*\.h') +) + +# From ppapi_cpp.gypi:ppapi_cpp_objects:cpp/dev/[^/]*\.h +cpp_dev_headers = gyp_extract.GypTargetSources( + ppapi_cpp_gypi, 'ppapi_cpp_objects', 'cpp/dev/[^/]*\.h') + +# From ppapi_gl.gypi:ppapi_gles2:.*\.h +gles2_headers = gyp_extract.GypTargetSources( + ppapi_gl_gypi, 'ppapi_gles2', '.*\.h') + + +c_header_install = env.AddHeaderToSdk( + [os.path.join(ppapi_base, h) for h in c_headers], os.path.join('ppapi', 'c')) +c_dev_header_install = env.AddHeaderToSdk( + [os.path.join(ppapi_base, h) for h in c_dev_headers], + os.path.join('ppapi', 'c', 'dev')) +cpp_header_install = env.AddHeaderToSdk( + [os.path.join(ppapi_base, h) for h in cpp_headers], + os.path.join('ppapi', 'cpp')) +cpp_dev_header_install = env.AddHeaderToSdk( + [os.path.join(ppapi_base, h) for h in cpp_dev_headers], + os.path.join('ppapi', 'cpp', 'dev')) + +# TODO(dspringer): Remove these lines when trusted ppapi builds are no longer +# needed for debugging. +# -------- 8< Cut here -------- +# From ppapi_cpp.gypi:ppapi_cpp_objects:cpp/[^/]*\.cc +# From ppapi_cpp.gypi:ppapi_cpp:cpp/[^/]*\.cc +cpp_trusted_sources = ( + gyp_extract.GypTargetSources( + ppapi_cpp_gypi, 'ppapi_cpp_objects', 'cpp/[^/]*\.cc') + + gyp_extract.GypTargetSources( + ppapi_cpp_gypi, 'ppapi_cpp', 'cpp/[^/]*\.cc') +) + +cpp_trusted_source_install = env.AddHeaderToSdk( + [os.path.join(ppapi_base, cpp) for cpp in cpp_trusted_sources], + subdir=os.path.join('ppapi', 'cpp'), + base_dirs=[os.path.join(env['ROOT_DIR'], 'third_party')]) +# -------- 8< Cut here -------- + +#env.AddLibraryToSdk(['ppapi_cpp']) +env.Requires('ppapi_cpp', [c_header_install, + c_dev_header_install, + cpp_header_install, + cpp_dev_header_install, + # TODO(dspringer): Remove this when trusted ppapi + # builds are no longer needed. + cpp_trusted_source_install]) + +# GLES2 headers go into the GLES2 subdir, Khronos headers into the KHR subdir, +# EGL header go in the EGL subdir. +gl_base = os.path.join(ppapi_base, 'lib', 'gl', 'include') +egl_base = os.path.join(gl_base, 'EGL') +egl_header_install = env.AddHeaderToSdk([ + os.path.join(egl_base, 'egl.h'), + os.path.join(egl_base, 'eglext.h'), + os.path.join(egl_base, 'eglplatform.h'), + ], 'EGL') + +gles2_base = os.path.join(gl_base, 'GLES2') +gles2_header_install = env.AddHeaderToSdk([ + os.path.join(gles2_base, 'gl2.h'), + os.path.join(gles2_base, 'gl2ext.h'), + os.path.join(gles2_base, 'gl2platform.h'), + ], 'GLES2') +khr_header_install = env.AddHeaderToSdk([ + os.path.join(gl_base, 'KHR', 'khrplatform.h'), + ], 'KHR') +ppapi_gles2_header_install = env.AddHeaderToSdk( + [os.path.join(ppapi_base, h) for h in gles2_headers], + os.path.join('ppapi', 'gles2')) +#env.AddLibraryToSdk(['ppapi_gles2']) +env.Requires('ppapi_gles2', [ + egl_header_install, + gles2_header_install, + khr_header_install, + ppapi_gles2_header_install, +]) + + +#------------------------------------------------------------------------------ +# Build Native Client components that are not included in the toolchain zip + +def build_nacl_tools(env, target, source): + ''' Tool for running make_nacl_tools + This builds sel_ldr, ncval, and the nacl libraries''' + build_utils.BotAnnotator().BuildStep('build NaCl tools') + for key, toolchain in env['NACL_TOOLCHAIN_ROOTS'].items(): + (_, variant) = key + make_nacl_tools_args = [ + '--toolchain', + toolchain, + '--jobs', + env['JOB_COUNT'], + '--nacl_dir', + os.path.join(env['ROOT_DIR'], 'third_party', 'native_client'), + ] + if env.GetOption('clean'): + make_nacl_tools_args.extend(['--clean']) + args = make_nacl_tools_args + ['--lib=%s' % variant] + print 'Running make_nacl_tools with ', args + sys.stdout.flush() + make_nacl_tools.main(args) + +exe = '.exe' if sys.platform in ['cygwin', 'win32'] else '' + +tools = [ + os.path.join('bin', 'sel_ldr_x86_32%s' % exe), + os.path.join('bin', 'sel_ldr_x86_64%s' % exe), + os.path.join('bin', 'ncval_x86_32%s' % exe), + os.path.join('bin', 'ncval_x86_64%s' % exe), + os.path.join('runtime', 'irt_core_x86_32.nexe'), + os.path.join('runtime', 'irt_core_x86_64.nexe'), + ] + +all_tools = [] +for dir in env['NACL_TOOLCHAIN_ROOTS'].values(): + all_tools += [os.path.join(dir, tool) for tool in tools] + +nacl_tools_cmd = env.Command(all_tools, + ['make_nacl_tools.py', + os.path.join(env['ROOT_DIR'], 'DEPS')], + build_nacl_tools) +env.Depends(nacl_tools_cmd, env.GetHeadersNode()) +env.Depends(env.GetToolchainNode(), nacl_tools_cmd) +env.AddCleanAction([], build_nacl_tools, ['toolchain', 'bot'], nacl_tools_cmd) diff --git a/native_client_sdk/src/build_tools/build_utils.py b/native_client_sdk/src/build_tools/build_utils.py new file mode 100644 index 0000000..4f92e03 --- /dev/null +++ b/native_client_sdk/src/build_tools/build_utils.py @@ -0,0 +1,307 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Small utility library of python functions used by the various package +installers. +""" + +import datetime +import errno +import fileinput +import os +import platform +import re +import shutil +import subprocess +import sys + +from nacl_sdk_scons import nacl_utils + + +def ChromeMilestone(): + '''Extract chrome_milestone from src/DEPS + + Returns: + Chrome milestone variable value from src/DEPS. + ''' + parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + def From(x, y): + return '' + def Var(x): + return '' + def File(x): + return '' + with open(os.path.join(parent_dir, 'DEPS'), 'r') as fh: + exec(fh.read()) + return vars.get('chrome_milestone') + + +#------------------------------------------------------------------------------ +# Parameters + +# Revision numbers for the SDK +PLATFORM_VERSION = 'pepper_' + ChromeMilestone() + +TOOLCHAIN_AUTODETECT = "AUTODETECT" + +#------------------------------------------------------------------------------ +# Functions + +# Make all the directories in |abs_path|. If |abs_path| points to a regular +# file, it is removed before an attempt to make the directories. If |abs_path| +# already points to a directory, this method does nothing. +def ForceMakeDirs(abs_path, mode=0755): + if os.path.isdir(abs_path): + return + try: + # Remove an existing regular file; ignore errors (e.g. file doesn't exist). + # If there are permission problems, they will be caught by the exception + # handler around the os.makedirs call. + os.remove(abs_path) + except: + pass + try: + os.makedirs(abs_path, mode) + except OSError, (os_errno, os_strerr): + # If the error is anything but EEXIST (file already exists), then print an + # informative message and re-raise. It is not and error if the directory + # already exists. + if os_errno != errno.EEXIST: + print 'ForceMakeDirs(%s, 0%o) FAILED: %s' % (abs_path, mode, os_strerr) + raise + pass + + +# patch version 2.6 doesn't work. Most of our Linux distros use patch 2.6. +# Returns |True| if the version of patch is usable (that is, not version 2.6). +# |shell_env| is the enviromnent used to run the subprocesses like patch and +# sed. If |shell_env| is None, then os.environ is used. +def CheckPatchVersion(shell_env=None): + if shell_env is None: + shell_env = os.environ + patch = subprocess.Popen("patch --version", + shell=True, + env=shell_env, + stdout=subprocess.PIPE) + sed = subprocess.Popen("sed q", + shell=True, + env=shell_env, + stdin=patch.stdout, + stdout=subprocess.PIPE) + sed_output = sed.communicate()[0] + if sed_output.strip() == 'patch 2.6': + print "patch 2.6 is incompatible with these scripts." + print "Please install either version 2.5.9 (or earlier)" + print "or version 2.6.1 (or later)." + return False + return True + + +# Build a toolchain path based on the platform type. |base_dir| is the root +# directory which includes the platform-specific toolchain. This could be +# something like "/usr/local/mydir/nacl_sdk/src". If |base_dir| is None, then +# the environment variable NACL_SDK_ROOT is used (if it's set). +# This method assumes that the platform-specific toolchain is found under +# <base_dir>/toolchain/<platform_variant>. +def NormalizeToolchain(toolchain=TOOLCHAIN_AUTODETECT, + base_dir=None, + arch=nacl_utils.DEFAULT_TOOLCHAIN_ARCH, + variant=nacl_utils.DEFAULT_TOOLCHAIN_VARIANT): + if toolchain == TOOLCHAIN_AUTODETECT: + if base_dir is None: + base_dir = os.getenv('NACL_SDK_ROOT', '') + normalized_toolchain = nacl_utils.ToolchainPath(base_dir=base_dir, + arch=arch, + variant=variant) + else: + normalized_toolchain = os.path.abspath(toolchain) + return normalized_toolchain + + +def SupportedNexeBitWidths(): + '''Return a list of .nexe bit widths that are supported by the host. + + Each supported bit width means the host can run a .nexe with the corresponding + instruction set architecture. For example, if this function returns the + list [32, 64], then the host can run both 32- and 64-bit .nexes. + + Note: on Windows, environment variables are used to determine the host's bit + width instead of the |platform| package. This is because (up until python + 2.7) the |platform| package returns the bit-width used to build python, not + the host's bit width. + + Returns: A list of supported nexe word widths (in bits) supported by the host + (typically 32 or 64). Returns an empty list if the word_width cannot be + determined. + ''' + bit_widths = [] + if sys.platform == 'win32': + # On Windows, the best way to detect the word size is to look at these + # env vars. python 2.6 and earlier on Windows (in particular the + # python on the Windows bots) generally always say they are 32-bits, + # even though the host is 64-bits. See this thread for more: + # http://stackoverflow.com/questions/7164843/ + # in-python-how-do-you-determine-whether-the-kernel-is-running-in-\ + # 32-bit-or-64-bit + if ('64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or + '64' in os.environ.get('PROCESSOR_ARCHITEW6432', '')): + bit_widths = [64] + else: + bit_widths = [32] + elif sys.platform == 'darwin': + # Mac can handle 32- and 64-bit .nexes. + bit_widths = [32, 64] + else: + # Linux 64 can handle both 32- and 64-bit. + machine = platform.machine() + bit_widths = [32, 64] if '64' in machine else [32] + + return bit_widths + + +def RawVersion(): + '''Returns the Raw version number of the SDK in a dotted format''' + return '.'.join(GetVersionNumbers()) + + +def GetVersionNumbers(): + '''Returns a list of 3 strings containing the version identifier''' + rev = str(SVNRevision()) + return [PLATFORM_VERSION, rev] + + +def SVNRevision(): + '''Returns the Subversion revision of this file. + + This file either needs to be in either a subversion repository or + a git repository that is sync'd to a subversion repository using git-svn.''' + run_path = os.path.dirname(os.path.abspath(__file__)) + p = subprocess.Popen('svn info', shell=True, stdout=subprocess.PIPE, + cwd=run_path) + if p.wait() != 0: + p = subprocess.Popen('git svn info', shell=True, stdout=subprocess.PIPE, + cwd=run_path) + if p.wait() != 0: + raise AssertionError('Cannot determine SVN revision of this repository'); + + svn_info = p.communicate()[0] + m = re.search('Revision: ([0-9]+)', svn_info) + if m: + return int(m.group(1)) + else: + raise AssertionError('Cannot extract revision number from svn info') + + +def VersionString(): + '''Returns the version of native client based on the svn revision number.''' + return 'native_client_sdk_%s' % '_'.join(GetVersionNumbers()) + + +def JoinPathToNaClRepo(*args, **kwargs): + '''Use os.path.join() to join the argument list to the NaCl repo location. + + Assumes that the Native Client repo is DEPSed into this repo under + third_party/native_client. This has to match the target dirs in the DEPS + file. + + If the key 'root_dir' is set, then this path is prepended to the NaCl repo + path. + + Args: + args: A list of path elements to append to the NaCl repo root. + kwargs: If the 'root_dir' key is present, this gets prepended to the + final path. + + Return: An OS-native path to the DEPSed in root of the NaCl repo. + ''' + nacl_path = os.path.join('third_party', 'native_client', *args) + root_path = kwargs.get('root_dir') + return os.path.join(root_path, nacl_path) if root_path else nacl_path + + +class BotAnnotator: + '''Interface to Bot Annotations + + See http://www.chromium.org/developers/testing/chromium-build-infrastructure/buildbot-annotations + ''' + + def __init__(self, stream=sys.stdout): + self._stream = stream + + def Print(self, message): + '''Display a message to the output stream and flush so the bots see it''' + self._stream.write("%s\n" % message) + self._stream.flush() + + def BuildStep(self, name): + self.Print("BUILD_STEP %s" % name) + # mball: Disabling buildbot annotations because it's more confusing than + # useful when running multiple jobs simultaneously. To re-enable + # annotations, using the following line instead of the previous: + # self.Print("@@@BUILD_STEP %s@@@" % name) + + def BuildStepFailure(self): + '''Signal a failure in the current build step to the annotator''' + self.Print("@@@STEP_FAILURE@@@") + + def Run(self, *popenargs, **kwargs): + '''Implements the functionality of subprocess.check_output, but also + prints out the command-line and the command output. + + Do not set stdout to anything because this function will redirect it + using a pipe. + + Arguments: + See subprocess.Popen + + returns: + a string containing the command output + ''' + if 'stdout' in kwargs or 'stderr' in kwargs: + raise ValueError('stdout or stderr argument not allowed.') + command = kwargs.get("args") + if command is None: + command = popenargs[0] + self.Print('Running %s' % command) + process = subprocess.Popen(stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + *popenargs, + **kwargs) + output, error_output = process.communicate() + if error_output: + self.Print("%s\nStdErr for %s:\n%s" % (output, command, error_output)) + else: + self.Print(output) + + retcode = process.poll() # Note - calling wait() can cause a deadlock + if retcode != 0: + raise subprocess.CalledProcessError(retcode, command) + return output + + #TODO(mball) Add the other possible build annotations, as needed + + +def UpdateReadMe(filename): + '''Updates the README file in the SDK with the current date and version''' + + for line in fileinput.input(filename, inplace=1): + sys.stdout.write(line.replace('${VERSION}', PLATFORM_VERSION) + .replace('${REVISION}', str(SVNRevision())) + .replace('${DATE}', str(datetime.date.today()))) + +def CleanDirectory(dir): + '''Cleans all the contents of a given directory. + This works even when there are Windows Junctions in the directory + + Args: + dir: The directory to clean + ''' + if sys.platform != 'win32': + shutil.rmtree(dir, ignore_errors=True) + else: + # Intentionally ignore return value since a directory might be in use. + subprocess.call(['rmdir', '/Q', '/S', dir], + env=os.environ.copy(), + shell=True) diff --git a/native_client_sdk/src/build_tools/buildbot_run.py b/native_client_sdk/src/build_tools/buildbot_run.py new file mode 100755 index 0000000..29ea596 --- /dev/null +++ b/native_client_sdk/src/build_tools/buildbot_run.py @@ -0,0 +1,78 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +'''Entry point for both build and try bots''' + +import os +import subprocess +import sys + +# Add scons to the python path (as nacl_utils.py requires it). +PARENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(os.path.join(PARENT_DIR, 'third_party/scons-2.0.1/engine')) + +import build_utils + + +def Archive(revision, chrome_milestone): + """Archive the sdk to google storage. + + Args: + revision: SDK svn revision number. + chrome_milestone: Chrome milestone (m14 etc), this is for. + """ + if sys.platform in ['cygwin', 'win32']: + src = 'nacl-sdk.exe' + dst = 'naclsdk_win.exe' + elif sys.platform in ['darwin']: + src = 'nacl-sdk.tgz' + dst = 'naclsdk_mac.tgz' + else: + src = 'nacl-sdk.tgz' + dst = 'naclsdk_linux.tgz' + bucket_path = 'nativeclient-mirror/nacl/nacl_sdk/pepper_%s_%s/%s' % ( + chrome_milestone, revision, dst) + full_dst = 'gs://%s' % bucket_path + subprocess.check_call( + '/b/build/scripts/slave/gsutil cp -a public-read %s %s' % ( + src, full_dst), shell=True) + url = 'https://commondatastorage.googleapis.com/%s' % bucket_path + print '@@@STEP_LINK@download@%s@@@' % url + sys.stdout.flush() + + +def main(argv): + if sys.platform in ['cygwin', 'win32']: + # Windows build + command = 'scons.bat' + else: + # Linux and Mac build + command = 'scons' + + parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + params = [os.path.join(parent_dir, command)] + argv + def Run(parameters): + print '\nRunning ', parameters + sys.stdout.flush() + subprocess.check_call(' '.join(parameters), shell=True, cwd=parent_dir) + + print '@@@BUILD_STEP generate sdk@@@' + sys.stdout.flush() + + Run(params + ['-c']) + Run(params + ['bot']) + + # Archive on non-trybots. + if '-sdk' in os.environ.get('BUILDBOT_BUILDERNAME', ''): + print '@@@BUILD_STEP archive build@@@' + sys.stdout.flush() + Archive(revision=os.environ.get('BUILDBOT_GOT_REVISION'), + chrome_milestone=build_utils.ChromeMilestone()) + + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/build.scons b/native_client_sdk/src/build_tools/debug_server/debug_stub/build.scons new file mode 100644 index 0000000..1fb4741 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/build.scons @@ -0,0 +1,37 @@ +# -*- python -*- +# Copyright 2010 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can +# be found in the LICENSE file. + +# This file needs to be in sync with $SOURCE_ROOT/ppapi/ppapi.gyp +# at the revision specified in $SOURCE_ROOT/native_client/DEPS. + +Import('env') + +debug_sources = [ + 'debug_stub.cc', + 'event_common.cc', + 'platform_common.cc', + 'transport_common.cc', + ] + +# TODO(noelallen) +# Still need to add Linux and Mac implementation. Move "common" to +# above section once their dependancies are satisfied. +if env.Bit('windows'): + debug_sources += [ + 'win/debug_stub_win.cc', + 'win/mutex_impl.cc', + 'win/platform_impl.cc', + 'win/thread_impl.cc', + ] +else: + debug_sources += [ + 'posix/debug_stub_posix.cc', + 'posix/mutex_impl.cc', + 'posix/platform_impl.cc', + 'posix/thread_impl.cc', + ] + + +env.DualLibrary('debug_stub', debug_sources) diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.cc new file mode 100644 index 0000000..1eb9770 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.cc @@ -0,0 +1,16 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include "native_client/src/debug_server/debug_stub/debug_stub.h" + +void NaClDebugStubInit() { + NaClDebugStubPlatformInit(); +} + +void NaClDebugStubFini() { + NaClDebugStubPlatformFini(); +} + diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.gyp b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.gyp new file mode 100644 index 0000000..c53ff04 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.gyp @@ -0,0 +1,170 @@ +# +# Copyright 2010 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can +# be found in the LICENSE file. +# +{ + 'includes': [ + '../../../build/common.gypi', + ], + 'variables': { + 'common_sources': [ + 'debug_stub.h', + 'debug_stub.cc', + 'event_common.cc', + 'platform_common.cc', + 'transport_common.cc', + ], + 'conditions': [ + ['OS=="linux" or OS=="mac"', { + 'platform_sources': [ + 'posix/debug_stub_posix.cc', + 'posix/mutex_impl.cc', + 'posix/platform_impl.cc', + 'posix/thread_impl.cc', + ], + }], + ['OS=="win"', { + 'platform_sources': [ + 'win/debug_stub_win.cc', + 'win/mutex_impl.cc', + 'win/platform_impl.cc', + 'win/thread_impl.cc', + ], + }], + ], + }, + + 'target_defaults': { + 'variables': { + 'target_base': 'none', + }, + 'target_conditions': [ + ['OS=="linux" or OS=="mac"', { + 'cflags': [ + '-Wno-long-long', + ], + }], + ['target_base=="debug_stub"', { + 'sources': [ + '<@(common_sources)', + '<@(platform_sources)', + ], + }], + ['target_base=="debug_stub_test"', { + 'sources': [ + 'debug_stub_test.cc', + ], + }], + ], + }, + 'targets': [ + # ---------------------------------------------------------------------- + { + 'target_name': 'debug_stub', + 'type': 'static_library', + 'variables': { + 'target_base': 'debug_stub', + }, + 'dependencies': [ + '<(DEPTH)/native_client/src/trusted/gdb_rsp/gdb_rsp.gyp:gdb_rsp', + ], + }, + # --------------------------------------------------------------------- + { + 'target_name': 'debug_stub_test', + 'type': 'executable', + 'variables': { + 'target_base': 'debug_stub_test', + }, + 'dependencies': [ + 'debug_stub', + ], + }, + ], + 'conditions': [ + ['OS=="win"', { + 'targets': [ + # --------------------------------------------------------------------- + { + 'target_name': 'debug_stub64', + 'type': 'static_library', + 'variables': { + 'target_base': 'debug_stub', + 'win_target': 'x64', + }, + 'dependencies': [ + '<(DEPTH)/native_client/src/trusted/gdb_rsp/gdb_rsp.gyp:gdb_rsp64', + ], + }, + # --------------------------------------------------------------------- + { + 'target_name': 'debug_stub_test64', + 'type': 'executable', + 'variables': { + 'target_base': 'debug_stub_test', + 'win_target': 'x64', + }, + 'dependencies': [ + 'debug_stub64', + ], + }, + # --------------------------------------------------------------------- + { + 'target_name': 'run_debug_stub_test', + 'message': 'running test run_imc_tests', + 'type': 'none', + 'dependencies': [ + 'debug_stub_test', + 'debug_stub_test64', + ], + 'actions': [ + { + 'action_name': 'run_debug_stub_test', + 'msvs_cygwin_shell': 0, + 'inputs': [ + '<(DEPTH)/native_client/tests/debug_stub/test_debug_stub.py', + '<(PRODUCT_DIR)/debug_stub_test', + ], + 'outputs': [ + '<(PRODUCT_DIR)/test-output/debug_stub_test.out', + ], + 'action': [ + '<@(python_exe)', + '<(DEPTH)/native_client/tests/debug_stub/test_debug_stub.py', + '<(PRODUCT_DIR)/debug_stub_test', + '>', + '<@(_outputs)', + ], + }, + ], + 'conditions': [ + ['MSVS_OS_BITS==64', { + 'actions': [ + { + 'action_name': 'run_debug_stub_test64', + 'msvs_cygwin_shell': 0, + 'inputs': [ + '<(DEPTH)/native_client/tests/debug_stub/test_debug_stub.py', + '<(PRODUCT_DIR)/debug_stub_test', + ], + 'outputs': [ + '<(PRODUCT_DIR)/test-output/debug_stub_test.out', + ], + 'action': [ + '<@(python_exe)', + '<(DEPTH)/native_client/tests/debug_stub/test_debug_stub.py', + '<(PRODUCT_DIR)/debug_stub_test64', + '>', + '<@(_outputs)', + ], + }, + ], + }], + ], + }, + ], + }], + ], +} + diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.h b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.h new file mode 100644 index 0000000..7d94d72 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.h @@ -0,0 +1,25 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#ifndef NATIVE_CLIENT_DEBUG_STUB_DEBUG_STUB_H_ +#define NATIVE_CLIENT_DEBUG_STUB_DEBUG_STUB_H_ + +#include "native_client/src/include/nacl_base.h" + +EXTERN_C_BEGIN + +void NaClDebugStubInit(); +void NaClDebugStubFini(); + +/* + * Platform-specific init/fini functions + */ +void NaClDebugStubPlatformInit(); +void NaClDebugStubPlatformFini(); + +EXTERN_C_END + +#endif /* NATIVE_CLIENT_DEBUG_STUB_DEBUG_STUB_H_ */ diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.rules b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.rules new file mode 100644 index 0000000..3ca1cae --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.rules @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioToolFile Name="debug_stub" Version="8.00"> + <Rules> + <CustomBuildRule AdditionalDependencies="..\..\..\..\native_client\tools\win_as.py;$(InputPath)" CommandLine="..\..\..\..\native_client\tools\win_py.cmd ..\..\..\..\native_client\tools\win_as.py -a $(PlatformName) -o $(IntDir)\$(InputName).obj -p ..\..\..\.. $(InputPath)" ExecutionDescription="Building assembly language file $(InputPath)" FileExtensions="S" Name="assembler (gnu-compatible)" Outputs="$(IntDir)\$(InputName).obj"/> + </Rules> +</VisualStudioToolFile> diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.sln b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.sln new file mode 100644 index 0000000..b399c1b --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub.sln @@ -0,0 +1,110 @@ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "(debug_stub)", "debug_stub", "{C44F267D-CA3D-2707-9AF9-6331B7BDD7F3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "(gdb_rsp)", "gdb_rsp", "{36DDA403-5397-3B36-4C08-47C4ED351C15}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "debug_stub", "debug_stub.vcproj", "{D9221BEA-DBFE-4677-4909-B88BA2E5A104}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "debug_stub64", "debug_stub64.vcproj", "{C3E9609B-A809-EF38-CA53-2962E844062E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "debug_stub_test", "debug_stub_test.vcproj", "{216DF189-70D2-3010-9D62-6AD9D13C62FD}" + ProjectSection(ProjectDependencies) = postProject + {D9221BEA-DBFE-4677-4909-B88BA2E5A104} = {D9221BEA-DBFE-4677-4909-B88BA2E5A104} + {0F659521-D577-1DFD-46FC-6DBEE3E1DFD6} = {0F659521-D577-1DFD-46FC-6DBEE3E1DFD6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "debug_stub_test64", "debug_stub_test64.vcproj", "{59F612B2-1E67-81E5-6F41-F56978638136}" + ProjectSection(ProjectDependencies) = postProject + {C3E9609B-A809-EF38-CA53-2962E844062E} = {C3E9609B-A809-EF38-CA53-2962E844062E} + {78AAD972-57EA-27B4-5F13-3CD2B7E8ABFC} = {78AAD972-57EA-27B4-5F13-3CD2B7E8ABFC} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gdb_rsp", "..\gdb_rsp\gdb_rsp.vcproj", "{0F659521-D577-1DFD-46FC-6DBEE3E1DFD6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gdb_rsp64", "..\gdb_rsp\gdb_rsp64.vcproj", "{78AAD972-57EA-27B4-5F13-3CD2B7E8ABFC}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "run_debug_stub_test", "run_debug_stub_test.vcproj", "{B7A7E685-394E-D4CF-B572-8A968A5E46AC}" + ProjectSection(ProjectDependencies) = postProject + {216DF189-70D2-3010-9D62-6AD9D13C62FD} = {216DF189-70D2-3010-9D62-6AD9D13C62FD} + {59F612B2-1E67-81E5-6F41-F56978638136} = {59F612B2-1E67-81E5-6F41-F56978638136} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|x64 = Release|x64 + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0F659521-D577-1DFD-46FC-6DBEE3E1DFD6}.Release|x64.ActiveCfg = Release|x64 + {0F659521-D577-1DFD-46FC-6DBEE3E1DFD6}.Release|x64.Build.0 = Release|x64 + {0F659521-D577-1DFD-46FC-6DBEE3E1DFD6}.Debug|Win32.ActiveCfg = Debug|Win32 + {0F659521-D577-1DFD-46FC-6DBEE3E1DFD6}.Debug|Win32.Build.0 = Debug|Win32 + {0F659521-D577-1DFD-46FC-6DBEE3E1DFD6}.Debug|x64.ActiveCfg = Debug|x64 + {0F659521-D577-1DFD-46FC-6DBEE3E1DFD6}.Debug|x64.Build.0 = Debug|x64 + {0F659521-D577-1DFD-46FC-6DBEE3E1DFD6}.Release|Win32.ActiveCfg = Release|Win32 + {0F659521-D577-1DFD-46FC-6DBEE3E1DFD6}.Release|Win32.Build.0 = Release|Win32 + {216DF189-70D2-3010-9D62-6AD9D13C62FD}.Release|x64.ActiveCfg = Release|x64 + {216DF189-70D2-3010-9D62-6AD9D13C62FD}.Release|x64.Build.0 = Release|x64 + {216DF189-70D2-3010-9D62-6AD9D13C62FD}.Debug|Win32.ActiveCfg = Debug|Win32 + {216DF189-70D2-3010-9D62-6AD9D13C62FD}.Debug|Win32.Build.0 = Debug|Win32 + {216DF189-70D2-3010-9D62-6AD9D13C62FD}.Debug|x64.ActiveCfg = Debug|x64 + {216DF189-70D2-3010-9D62-6AD9D13C62FD}.Debug|x64.Build.0 = Debug|x64 + {216DF189-70D2-3010-9D62-6AD9D13C62FD}.Release|Win32.ActiveCfg = Release|Win32 + {216DF189-70D2-3010-9D62-6AD9D13C62FD}.Release|Win32.Build.0 = Release|Win32 + {59F612B2-1E67-81E5-6F41-F56978638136}.Release|x64.ActiveCfg = Release|x64 + {59F612B2-1E67-81E5-6F41-F56978638136}.Release|x64.Build.0 = Release|x64 + {59F612B2-1E67-81E5-6F41-F56978638136}.Debug|Win32.ActiveCfg = Debug|x64 + {59F612B2-1E67-81E5-6F41-F56978638136}.Debug|Win32.Build.0 = Debug|x64 + {59F612B2-1E67-81E5-6F41-F56978638136}.Debug|x64.ActiveCfg = Debug|x64 + {59F612B2-1E67-81E5-6F41-F56978638136}.Debug|x64.Build.0 = Debug|x64 + {59F612B2-1E67-81E5-6F41-F56978638136}.Release|Win32.ActiveCfg = Release|x64 + {59F612B2-1E67-81E5-6F41-F56978638136}.Release|Win32.Build.0 = Release|x64 + {78AAD972-57EA-27B4-5F13-3CD2B7E8ABFC}.Release|x64.ActiveCfg = Release|x64 + {78AAD972-57EA-27B4-5F13-3CD2B7E8ABFC}.Release|x64.Build.0 = Release|x64 + {78AAD972-57EA-27B4-5F13-3CD2B7E8ABFC}.Debug|Win32.ActiveCfg = Debug|x64 + {78AAD972-57EA-27B4-5F13-3CD2B7E8ABFC}.Debug|Win32.Build.0 = Debug|x64 + {78AAD972-57EA-27B4-5F13-3CD2B7E8ABFC}.Debug|x64.ActiveCfg = Debug|x64 + {78AAD972-57EA-27B4-5F13-3CD2B7E8ABFC}.Debug|x64.Build.0 = Debug|x64 + {78AAD972-57EA-27B4-5F13-3CD2B7E8ABFC}.Release|Win32.ActiveCfg = Release|x64 + {78AAD972-57EA-27B4-5F13-3CD2B7E8ABFC}.Release|Win32.Build.0 = Release|x64 + {B7A7E685-394E-D4CF-B572-8A968A5E46AC}.Release|x64.ActiveCfg = Release|x64 + {B7A7E685-394E-D4CF-B572-8A968A5E46AC}.Release|x64.Build.0 = Release|x64 + {B7A7E685-394E-D4CF-B572-8A968A5E46AC}.Debug|Win32.ActiveCfg = Debug|Win32 + {B7A7E685-394E-D4CF-B572-8A968A5E46AC}.Debug|Win32.Build.0 = Debug|Win32 + {B7A7E685-394E-D4CF-B572-8A968A5E46AC}.Debug|x64.ActiveCfg = Debug|x64 + {B7A7E685-394E-D4CF-B572-8A968A5E46AC}.Debug|x64.Build.0 = Debug|x64 + {B7A7E685-394E-D4CF-B572-8A968A5E46AC}.Release|Win32.ActiveCfg = Release|Win32 + {B7A7E685-394E-D4CF-B572-8A968A5E46AC}.Release|Win32.Build.0 = Release|Win32 + {C3E9609B-A809-EF38-CA53-2962E844062E}.Release|x64.ActiveCfg = Release|x64 + {C3E9609B-A809-EF38-CA53-2962E844062E}.Release|x64.Build.0 = Release|x64 + {C3E9609B-A809-EF38-CA53-2962E844062E}.Debug|Win32.ActiveCfg = Debug|x64 + {C3E9609B-A809-EF38-CA53-2962E844062E}.Debug|Win32.Build.0 = Debug|x64 + {C3E9609B-A809-EF38-CA53-2962E844062E}.Debug|x64.ActiveCfg = Debug|x64 + {C3E9609B-A809-EF38-CA53-2962E844062E}.Debug|x64.Build.0 = Debug|x64 + {C3E9609B-A809-EF38-CA53-2962E844062E}.Release|Win32.ActiveCfg = Release|x64 + {C3E9609B-A809-EF38-CA53-2962E844062E}.Release|Win32.Build.0 = Release|x64 + {D9221BEA-DBFE-4677-4909-B88BA2E5A104}.Release|x64.ActiveCfg = Release|x64 + {D9221BEA-DBFE-4677-4909-B88BA2E5A104}.Release|x64.Build.0 = Release|x64 + {D9221BEA-DBFE-4677-4909-B88BA2E5A104}.Debug|Win32.ActiveCfg = Debug|Win32 + {D9221BEA-DBFE-4677-4909-B88BA2E5A104}.Debug|Win32.Build.0 = Debug|Win32 + {D9221BEA-DBFE-4677-4909-B88BA2E5A104}.Debug|x64.ActiveCfg = Debug|x64 + {D9221BEA-DBFE-4677-4909-B88BA2E5A104}.Debug|x64.Build.0 = Debug|x64 + {D9221BEA-DBFE-4677-4909-B88BA2E5A104}.Release|Win32.ActiveCfg = Release|Win32 + {D9221BEA-DBFE-4677-4909-B88BA2E5A104}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C3E9609B-A809-EF38-CA53-2962E844062E} = {C44F267D-CA3D-2707-9AF9-6331B7BDD7F3} + {59F612B2-1E67-81E5-6F41-F56978638136} = {C44F267D-CA3D-2707-9AF9-6331B7BDD7F3} + {D9221BEA-DBFE-4677-4909-B88BA2E5A104} = {C44F267D-CA3D-2707-9AF9-6331B7BDD7F3} + {216DF189-70D2-3010-9D62-6AD9D13C62FD} = {C44F267D-CA3D-2707-9AF9-6331B7BDD7F3} + {B7A7E685-394E-D4CF-B572-8A968A5E46AC} = {C44F267D-CA3D-2707-9AF9-6331B7BDD7F3} + {0F659521-D577-1DFD-46FC-6DBEE3E1DFD6} = {36DDA403-5397-3B36-4C08-47C4ED351C15} + {78AAD972-57EA-27B4-5F13-3CD2B7E8ABFC} = {36DDA403-5397-3B36-4C08-47C4ED351C15} + EndGlobalSection +EndGlobal diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub64.rules b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub64.rules new file mode 100644 index 0000000..09cae6f --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub64.rules @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioToolFile Name="debug_stub64" Version="8.00"> + <Rules> + <CustomBuildRule AdditionalDependencies="..\..\..\..\native_client\tools\win_as.py;$(InputPath)" CommandLine="..\..\..\..\native_client\tools\win_py.cmd ..\..\..\..\native_client\tools\win_as.py -a $(PlatformName) -o $(IntDir)\$(InputName).obj -p ..\..\..\.. $(InputPath)" ExecutionDescription="Building assembly language file $(InputPath)" FileExtensions="S" Name="assembler (gnu-compatible)" Outputs="$(IntDir)\$(InputName).obj"/> + </Rules> +</VisualStudioToolFile> diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub_test.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub_test.cc new file mode 100644 index 0000000..69aefde --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub_test.cc @@ -0,0 +1,13 @@ +/* + * Copyright 2008 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ +#include "native_client/src/include/portability.h" + +int main(int argc, char* argv[]) { + UNREFERENCED_PARAMETER(argc); + UNREFERENCED_PARAMETER(argv); + // TODO(noelallen): implement me + return 0; +} diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub_test.rules b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub_test.rules new file mode 100644 index 0000000..ba96b7c --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub_test.rules @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioToolFile Name="debug_stub_test" Version="8.00"> + <Rules> + <CustomBuildRule AdditionalDependencies="..\..\..\..\native_client\tools\win_as.py;$(InputPath)" CommandLine="..\..\..\..\native_client\tools\win_py.cmd ..\..\..\..\native_client\tools\win_as.py -a $(PlatformName) -o $(IntDir)\$(InputName).obj -p ..\..\..\.. $(InputPath)" ExecutionDescription="Building assembly language file $(InputPath)" FileExtensions="S" Name="assembler (gnu-compatible)" Outputs="$(IntDir)\$(InputName).obj"/> + </Rules> +</VisualStudioToolFile> diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub_test64.rules b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub_test64.rules new file mode 100644 index 0000000..279f11a --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/debug_stub_test64.rules @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioToolFile Name="debug_stub_test64" Version="8.00"> + <Rules> + <CustomBuildRule AdditionalDependencies="..\..\..\..\native_client\tools\win_as.py;$(InputPath)" CommandLine="..\..\..\..\native_client\tools\win_py.cmd ..\..\..\..\native_client\tools\win_as.py -a $(PlatformName) -o $(IntDir)\$(InputName).obj -p ..\..\..\.. $(InputPath)" ExecutionDescription="Building assembly language file $(InputPath)" FileExtensions="S" Name="assembler (gnu-compatible)" Outputs="$(IntDir)\$(InputName).obj"/> + </Rules> +</VisualStudioToolFile> diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/event_common.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/event_common.cc new file mode 100644 index 0000000..5e85c81 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/event_common.cc @@ -0,0 +1,68 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include "native_client/src/shared/platform/nacl_sync.h" + +/* + * Define the common gdb_utils IEvent interface to use the NaCl version. + * IEvent only suports single trigger Signal API. + */ + +#if NACL_WINDOWS +# include "native_client/src/shared/platform/win/lock.h" +# include "native_client/src/shared/platform/win/condition_variable.h" +#elif NACL_LINUX || NACL_OSX +# include "native_client/src/shared/platform/linux/lock.h" +# include "native_client/src/shared/platform/linux/condition_variable.h" +#endif + +#include "native_client/src/debug_server/port/event.h" + +namespace port { + +class Event : public IEvent { + public: + Event() : signaled_(false) { } + ~Event() {} + + void Wait() { + /* Start the wait owning the lock */ + lock_.Acquire(); + + /* We can skip this if already signaled */ + while (!signaled_) { + /* Otherwise, try and wait, which release the lock */ + cond_.Wait(lock_); + + /* We exit the wait owning the lock again. */ + }; + + /* Clear the signal then unlock. */ + signaled_ = false; + lock_.Release(); + } + + void Signal() { + signaled_ = true; + cond_.Signal(); + } + + public: + volatile bool signaled_; + NaCl::Lock lock_; + NaCl::ConditionVariable cond_; +}; + +IEvent* IEvent::Allocate() { + return new Event; +} + +void IEvent::Free(IEvent *ievent) { + delete static_cast<Event*>(ievent); +} + +} // End of port namespace + diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/linux/debug_stub_linux.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/linux/debug_stub_linux.cc new file mode 100644 index 0000000..00e0926 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/linux/debug_stub_linux.cc @@ -0,0 +1,15 @@ +/* + * Copyright 2008 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ +#include "native_client/src/trusted/debug_stub/debug_stub.h" + +void NaClDebugStubPlatformInit() { + // TODO(noelallen): implement me +} + +void NaClDebugStubPlatformFini() { + // TODO(noelallen): implement me +} + diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/osx/debug_stub_osx.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/osx/debug_stub_osx.cc new file mode 100644 index 0000000..00e0926 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/osx/debug_stub_osx.cc @@ -0,0 +1,15 @@ +/* + * Copyright 2008 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ +#include "native_client/src/trusted/debug_stub/debug_stub.h" + +void NaClDebugStubPlatformInit() { + // TODO(noelallen): implement me +} + +void NaClDebugStubPlatformFini() { + // TODO(noelallen): implement me +} + diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/platform_common.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/platform_common.cc new file mode 100644 index 0000000..4c9f098 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/platform_common.cc @@ -0,0 +1,40 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <map> + +#include "native_client/src/shared/platform/nacl_log.h" +#include "native_client/src/debug_server/gdb_rsp/abi.h" +#include "native_client/src/debug_server/port/platform.h" + +namespace port { + +// Log a message +void IPlatform::LogInfo(const char *fmt, ...) { + va_list argptr; + va_start(argptr, fmt); + + NaClLogV(LOG_INFO, fmt, argptr); +} + +void IPlatform::LogWarning(const char *fmt, ...) { + va_list argptr; + va_start(argptr, fmt); + + NaClLogV(LOG_WARNING, fmt, argptr); +} + +void IPlatform::LogError(const char *fmt, ...) { + va_list argptr; + va_start(argptr, fmt); + + NaClLogV(LOG_ERROR, fmt, argptr); +} + +} // End of port namespace diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/posix/debug_stub_posix.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/posix/debug_stub_posix.cc new file mode 100644 index 0000000..8d8558e --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/posix/debug_stub_posix.cc @@ -0,0 +1,12 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include "native_client/src/trusted/debug_stub/debug_stub.h" + +// No platform specific initialization need for POSIX +void NaClDebugStubPlatformInit() {} +void NaClDebugStubPlatformFini() {} + diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/posix/mutex_impl.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/posix/mutex_impl.cc new file mode 100644 index 0000000..f2548d7 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/posix/mutex_impl.cc @@ -0,0 +1,58 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <pthread.h> + +#include "native_client/src/debug_server/port/mutex.h" + +/* + * Unfortunately NaClSync does not have the correct recursive + * property so we need to use our own version. + */ + +namespace port { + +class Mutex : public IMutex { + public: + Mutex() { + pthread_mutexattr_t attr; + + // Create a recursive mutex, so we can reenter on the same thread + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mutex_, &attr); + pthread_mutexattr_destroy(&attr); + } + + ~Mutex() { + pthread_mutex_destroy(&mutex_); + } + + virtual void Lock() { + pthread_mutex_lock(&mutex_); + } + + virtual bool Try() { + return pthread_mutex_trylock(&mutex_) == 0; + } + + virtual void Unlock() { + pthread_mutex_unlock(&mutex_); + } + + public: + pthread_mutex_t mutex_; +}; + +IMutex* IMutex::Allocate() { + return new Mutex; +} + +void IMutex::Free(IMutex *mutex) { + delete static_cast<Mutex*>(mutex); +} + +} // namespace port diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/posix/platform_impl.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/posix/platform_impl.cc new file mode 100644 index 0000000..d6e0727 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/posix/platform_impl.cc @@ -0,0 +1,106 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/syscall.h> +#include <pthread.h> + +#include <map> +#include <vector> + +#include "native_client/src/shared/platform/nacl_log.h" +#include "native_client/src/debug_server/gdb_rsp/abi.h" +#include "native_client/src/debug_server/gdb_rsp/util.h" +#include "native_client/src/debug_server/port/event.h" +#include "native_client/src/debug_server/port/platform.h" + +#include "native_client/src/trusted/service_runtime/nacl_config.h" +#include "native_client/src/trusted/service_runtime/sel_ldr.h" +#include "native_client/src/trusted/service_runtime/sel_rt.h" + + +/* + * Define the OS specific portions of gdb_utils IPlatform interface. + */ + + +// TODO(noelallen) : Add POSIX implementation. These functions +// represent a minimal implementation to allow the debugging +// code to link and run. +static port::IEvent* GetLaunchEvent() { + static port::IEvent* event_ = port::IEvent::Allocate(); + return event_; +} + +namespace port { + +struct StartInfo_t { + port::IPlatform::ThreadFunc_t func_; + void *cookie_; + volatile uint32_t id_; +}; + +// Get the OS id of this thread +uint32_t IPlatform::GetCurrentThread() { + return static_cast<uint32_t>(syscall(SYS_gettid)); +} + +// Use start stub, to record thread id, and signal launcher +static void *StartFunc(void* cookie) { + StartInfo_t* info = reinterpret_cast<StartInfo_t*>(cookie); + info->id_ = (uint32_t) syscall(SYS_gettid); + + printf("Started thread...\n"); + GetLaunchEvent()->Signal(); + info->func_(info->cookie_); + + return NULL; +} + +uint32_t IPlatform::CreateThread(ThreadFunc_t func, void* cookie) { + pthread_t thread; + StartInfo_t info; + + // Setup the thread information + info.func_ = func; + info.cookie_ = cookie; + + printf("Creating thread...\n"); + + // Redirect to stub and wait for signal before continuing + if (pthread_create(&thread, NULL, StartFunc, &info) == 0) { + GetLaunchEvent()->Wait(); + printf("Found thread...\n"); + return info.id_; + } + + return 0; +} + +void IPlatform::Relinquish(uint32_t msec) { + usleep(msec * 1000); +} + +bool IPlatform::GetMemory(uint64_t virt, uint32_t len, void *dst) { + UNREFERENCED_PARAMETER(virt); + UNREFERENCED_PARAMETER(len); + UNREFERENCED_PARAMETER(dst); + return false; +} + +bool IPlatform::SetMemory(uint64_t virt, uint32_t len, void *src) { + UNREFERENCED_PARAMETER(virt); + UNREFERENCED_PARAMETER(len); + UNREFERENCED_PARAMETER(src); + return false; +} + +} // End of port namespace + diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/posix/thread_impl.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/posix/thread_impl.cc new file mode 100644 index 0000000..f8bdfa1 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/posix/thread_impl.cc @@ -0,0 +1,123 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <stdexcept> + +#include "native_client/src/shared/platform/nacl_log.h" +#include "native_client/src/debug_server/port/mutex.h" +#include "native_client/src/debug_server/port/thread.h" + +/* + * Define the OS specific portions of gdb_utils IThread interface. + */ + +namespace port { + +static IMutex* ThreadGetLock() { + static IMutex* mutex_ = IMutex::Allocate(); + return mutex_; +} + +static IThread::ThreadMap_t *ThreadGetMap() { + static IThread::ThreadMap_t* map_ = new IThread::ThreadMap_t; + return map_; +} + +// TODO(noelallen) : Add POSIX implementation. These functions +// represent a minimal implementation to allow the debugging +// code to link and run. +class Thread : public IThread { + public: + explicit Thread(uint32_t id) : ref_(1), id_(id), state_(DEAD) {} + ~Thread() {} + + uint32_t GetId() { + return id_; + } + + State GetState() { + return state_; + } + + virtual bool Suspend() { + return false; + } + + virtual bool Resume() { + return false; + } + + virtual bool SetStep(bool on) { + UNREFERENCED_PARAMETER(on); + return false; + } + + virtual bool GetRegister(uint32_t index, void *dst, uint32_t len) { + UNREFERENCED_PARAMETER(index); + UNREFERENCED_PARAMETER(dst); + UNREFERENCED_PARAMETER(len); + return false; + } + + virtual bool SetRegister(uint32_t index, void* src, uint32_t len) { + UNREFERENCED_PARAMETER(index); + UNREFERENCED_PARAMETER(src); + UNREFERENCED_PARAMETER(len); + return false; + } + + virtual void* GetContext() { return NULL; } + + private: + uint32_t ref_; + uint32_t id_; + State state_; + + friend class IThread; +}; + +IThread* IThread::Acquire(uint32_t id, bool create) { + MutexLock lock(ThreadGetLock()); + Thread* thread; + ThreadMap_t &map = *ThreadGetMap(); + + // Check if we have that thread + if (map.count(id)) { + thread = static_cast<Thread*>(map[id]); + thread->ref_++; + return thread; + } + + // If not, can we create it? + if (create) { + // If not add it to the map + thread = new Thread(id); + map[id] = thread; + return thread; + } + + return NULL; +} + +void IThread::Release(IThread *ithread) { + MutexLock lock(ThreadGetLock()); + Thread* thread = static_cast<Thread*>(ithread); + thread->ref_--; + + if (thread->ref_ == 0) { + ThreadGetMap()->erase(thread->id_); + delete static_cast<IThread*>(thread); + } +} + +void IThread::SetExceptionCatch(IThread::CatchFunc_t func, void *cookie) { + UNREFERENCED_PARAMETER(func); + UNREFERENCED_PARAMETER(cookie); +} + + +} // End of port namespace + diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/run_debug_stub_test.rules b/native_client_sdk/src/build_tools/debug_server/debug_stub/run_debug_stub_test.rules new file mode 100644 index 0000000..2e368f4 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/run_debug_stub_test.rules @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioToolFile Name="run_debug_stub_test" Version="8.00"> + <Rules> + <CustomBuildRule AdditionalDependencies="..\..\..\..\native_client\tools\win_as.py;$(InputPath)" CommandLine="..\..\..\..\native_client\tools\win_py.cmd ..\..\..\..\native_client\tools\win_as.py -a $(PlatformName) -o $(IntDir)\$(InputName).obj -p ..\..\..\.. $(InputPath)" ExecutionDescription="Building assembly language file $(InputPath)" FileExtensions="S" Name="assembler (gnu-compatible)" Outputs="$(IntDir)\$(InputName).obj"/> + </Rules> +</VisualStudioToolFile> diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/transport_common.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/transport_common.cc new file mode 100644 index 0000000..191b72c --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/transport_common.cc @@ -0,0 +1,295 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#ifdef _WIN32 +#include <windows.h> +#ifndef AF_IPX +#include <winsock2.h> +#endif +#define SOCKET_HANDLE SOCKET +#else + +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#define SOCKET_HANDLE int +#define closesocket close +#endif + +#include <stdlib.h> +#include <string> + +#include "native_client/src/debug_server/gdb_rsp/util.h" +#include "native_client/src/debug_server/port/platform.h" +#include "native_client/src/debug_server/port/transport.h" + +using gdb_rsp::stringvec; +using gdb_rsp::StringSplit; + +namespace port { + +typedef int socklen_t; + +class Transport : public ITransport { + public: + Transport() { + handle_ = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + } + + explicit Transport(SOCKET_HANDLE s) { + handle_ = s; + } + + ~Transport() { + if (handle_ != -1) closesocket(handle_); + } + + // Read from this transport, return a negative value if there is an error + // otherwise return the number of bytes actually read. + virtual int32_t Read(void *ptr, int32_t len) { + return ::recv(handle_, reinterpret_cast<char *>(ptr), len, 0); + } + + // Write to this transport, return a negative value if there is an error + // otherwise return the number of bytes actually written. + virtual int32_t Write(const void *ptr, int32_t len) { + return ::send(handle_, reinterpret_cast<const char *>(ptr), len, 0); + } + + // Return true if data becomes availible or false after ms milliseconds. + virtual bool ReadWaitWithTimeout(uint32_t ms = 0) { + fd_set fds; + + FD_ZERO(&fds); + FD_SET(handle_, &fds); + + // We want a "non-blocking" check + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec= ms * 1000; + + // Check if this file handle can select on read + int cnt = select(0, &fds, 0, 0, &timeout); + + // If we are ready, or if there is an error. We return true + // on error, to "timeout" and let the next IO request fail. + if (cnt != 0) return true; + + return false; + } + +// On windows, the header that defines this has other definition +// colitions, so we define it outselves just in case +#ifndef SD_BOTH +#define SD_BOTH 2 +#endif + + virtual void Disconnect() { + // Shutdown the conneciton in both diections. This should + // always succeed, and nothing we can do if this fails. + (void) ::shutdown(handle_, SD_BOTH); + } + + protected: + SOCKET_HANDLE handle_; +}; + +// Convert string in the form of [addr][:port] where addr is a +// IPv4 address or host name, and port is a 16b tcp/udp port. +// Both portions are optional, and only the portion of the address +// provided is updated. Values are provided in network order. +static bool StringToIPv4(const std::string &instr, uint32_t *addr, + uint16_t *port) { + // Make a copy so the are unchanged unless we succeed + uint32_t outaddr = *addr; + uint16_t outport = *port; + + // Substrings of the full ADDR:PORT + std::string addrstr; + std::string portstr; + + // We should either have one or two tokens in the form of: + // IP - IP, NUL + // IP: - IP, NUL + // :PORT - NUL, PORT + // IP:PORT - IP, PORT + + // Search for the port marker + size_t portoff = instr.find(':'); + + // If we found a ":" before the end, get both substrings + if ((portoff != std::string::npos) && (portoff + 1 < instr.size())) { + addrstr = instr.substr(0, portoff); + portstr = instr.substr(portoff + 1, std::string::npos); + } else { + // otherwise the entire string is the addr portion. + addrstr = instr; + portstr = ""; + } + + // If the address portion was provided, update it + if (addrstr.size()) { + // Special case 0.0.0.0 which means any IPv4 interface + if (addrstr == "0.0.0.0") { + outaddr = 0; + } else { + struct hostent *host = gethostbyname(addrstr.data()); + + // Check that we found an IPv4 host + if ((NULL == host) || (AF_INET != host->h_addrtype)) return false; + + // Make sure the IP list isn't empty. + if (0 == host->h_addr_list[0]) return false; + + // Use the first one. + uint32_t *addrarray = reinterpret_cast<uint32_t*>(host->h_addr_list); + outaddr = addrarray[0]; + } + } + + // if the port portion was provided, then update it + if (portstr.size()) { + int val = atoi(portstr.data()); + if ((val < 0) || (val > 65535)) return false; + outport = ntohs(static_cast<uint16_t>(val)); + } + + // We haven't failed, so set the values + *addr = outaddr; + *port = outport; + return true; +} + + +static SOCKET_HANDLE s_ServerSock; + +void Init() +{ + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD( 1, 1 ); + int res = ::WSAStartup( wVersionRequested, &wsaData ); + printf("::WSAStartup -> %d\n", res); +} + + +static bool SocketInit() { + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + Init(); + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + long sc = (long)::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + s_ServerSock = sc; + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + if (s_ServerSock == -1) { + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + IPlatform::LogError("Failed to create socket.\n"); + return false; + } + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + + return true; +} + +static bool SocketsAvailable() { + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + static bool _init = SocketInit(); + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + return _init; +} + +static bool BuildSockAddr(const char *addr, struct sockaddr_in *sockaddr) { + std::string addrstr = addr; + uint32_t *pip = reinterpret_cast<uint32_t*>(&sockaddr->sin_addr.s_addr); + uint16_t *pport = reinterpret_cast<uint16_t*>(&sockaddr->sin_port); + + sockaddr->sin_family = AF_INET; + return StringToIPv4(addrstr, pip, pport); +} + +ITransport* ITransport::Connect(const char *addr) { + if (!SocketsAvailable()) return NULL; + + SOCKET_HANDLE s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s == -1) { + IPlatform::LogError("Failed to create connection socket.\n"); + return NULL; + } + + struct sockaddr_in saddr; + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = htonl(0x7F000001); + saddr.sin_port = htons(4014); + + // Override portions address that are provided + if (addr) BuildSockAddr(addr, &saddr); + + if (::connect(s, reinterpret_cast<sockaddr*>(&saddr), sizeof(saddr)) != 0) { + IPlatform::LogError("Failed to connect.\n"); + return NULL; + } + + return new Transport(s); +} + +ITransport* ITransport::Accept(const char *addr) { + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + static bool listening = false; + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + if (!SocketsAvailable()) return NULL; + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + + if (!listening) { + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + struct sockaddr_in saddr; + socklen_t addrlen = static_cast<socklen_t>(sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = htonl(0x7F000001); + saddr.sin_port = htons(4014); + + // Override portions address that are provided + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + if (addr) BuildSockAddr(addr, &saddr); + + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + struct sockaddr *psaddr = reinterpret_cast<struct sockaddr *>(&saddr); + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + if (bind(s_ServerSock, psaddr, addrlen)) { + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + IPlatform::LogError("Failed to bind server.\n"); + return NULL; + } + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + + if (listen(s_ServerSock, 1)) { + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + IPlatform::LogError("Failed to listen.\n"); + return NULL; + } + printf("---> %d %s\n", __LINE__, __FILE__); fflush(stdout); + + listening = true; + } + + if (listening) { + SOCKET_HANDLE s = ::accept(s_ServerSock, NULL, 0); + if (-1 != s) return new Transport(s); + return NULL; + } + + return NULL; +} + +void ITransport::Free(ITransport* itrans) { + Transport* trans = static_cast<Transport*>(itrans); + delete trans; +} + +} // namespace port + diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/win/debug_stub_win.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/win/debug_stub_win.cc new file mode 100644 index 0000000..147799e --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/win/debug_stub_win.cc @@ -0,0 +1,47 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <windows.h> + +#ifndef AF_IPX +#include <winsock2.h> +#endif + +#include "native_client/src/debug_server/debug_stub/debug_stub.h" +#include "native_client/src/debug_server/port/platform.h" + + +using port::IPlatform; + +void NaClDebugStubPlatformInit() { + WORD wVersionRequested; + WSADATA wsaData; + int err; + + // Make sure to request the use of sockets. + // NOTE: It is safe to call Startup multiple times + wVersionRequested = MAKEWORD(2, 2); + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + // We could not find a matching DLL + IPlatform::LogError("WSAStartup failed with error: %d\n", err); + exit(-1); + } + + if (HIBYTE(wsaData.wVersion) != 2) { + // We couldn't get a matching version + IPlatform::LogError("Could not find a usable version of Winsock.dll\n"); + WSACleanup(); + exit(-1); + } +} + +void NaClDebugStubPlatformFini() { + WSACleanup(); +} + diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/win/mutex_impl.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/win/mutex_impl.cc new file mode 100644 index 0000000..c9757ee --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/win/mutex_impl.cc @@ -0,0 +1,89 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <windows.h> +#include <exception> + +#include "native_client/src/debug_server/port/mutex.h" + +/* + * Define the gdb_utils IMutex interface to use the NaCl version. + * Unfortunately NaClSync does not have the correct recursive + * property so we need to use our own version. + */ + +namespace port { + +class Mutex : public IMutex { + public: + Mutex() : handle_(NULL) { + handle_ = CreateMutex(NULL, FALSE, NULL); + + // If we fail to create the mutex, then abort the app. + if (NULL == handle_) throw std::exception("Failed to create mutex."); + } + + ~Mutex() { + // If constructor where to throw, then the mutex could be unset + if (NULL == handle_) return; + + // This should always succeed. + (void) CloseHandle(handle_); + } + + void Lock() { + DWORD ret; + do { + ret = WaitForSingleObject(handle_, INFINITE); + if (WAIT_OBJECT_0 == ret) return; + + // If this lock wasn't abandoned or locked + // the there must be an error. + if (WAIT_ABANDONED != ret) { + throw std::exception("Lock failed on mutex."); + } + } while (WAIT_ABANDONED == ret); + } + + bool Try() { + DWORD ret = WaitForSingleObject(handle_, 0); + switch (ret) { + case WAIT_OBJECT_0: + return true; + + // The lock was already taken, we will need to retry. + case WAIT_TIMEOUT: + return false; + + // The lock was abandoned and ownership transfered, it is still unlocked + // so we can just retry. + case WAIT_ABANDONED: + return false; + + // Unrecoverable error. + default: + throw std::exception("Try failed on mutex."); + } + } + + void Mutex::Unlock() { + (void) ReleaseMutex(handle_); + } + + private: + HANDLE handle_; +}; + +IMutex* IMutex::Allocate() { + return new Mutex; +} + +void IMutex::Free(IMutex *mutex) { + delete static_cast<Mutex*>(mutex); +} + +} // End of port namespace + diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/win/platform_impl.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/win/platform_impl.cc new file mode 100644 index 0000000..d585968 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/win/platform_impl.cc @@ -0,0 +1,105 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <process.h> +#include <stdio.h> +#include <stdlib.h> +#include <windows.h> + +#include <exception> + +#include "native_client/src/shared/platform/nacl_log.h" +#include "native_client/src/debug_server/gdb_rsp/abi.h" +#include "native_client/src/debug_server/port/platform.h" + +/* + * Define the OS specific portions of gdb_utils IPlatform interface. + */ +int GetLastErrorString(char* buffer, size_t length) { + DWORD error = GetLastError(); + return FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buffer, + static_cast<DWORD>((64 * 1024 < length) ? 64 * 1024 : length), + NULL) ? 0 : -1; +} + + +static DWORD Reprotect(void *ptr, uint32_t len, DWORD newflags) { + DWORD oldflags; + if (!VirtualProtect(ptr, len, newflags, &oldflags)) { + char msg[256] = {0}; + GetLastErrorString(msg, sizeof(msg) - 1); + msg[sizeof(msg) - 1] = 0; + printf("Failed with %d [%s]\n", GetLastError(), msg); + return -1; + } + + FlushInstructionCache(GetCurrentProcess(), ptr, len); + return oldflags; +} + +namespace port { + +// Called to request the platform start/stop the thread +uint32_t IPlatform::GetCurrentThread() { + return static_cast<uint32_t>(GetCurrentThreadId()); +} + +/* + * Since the windows compiler does not use __stdcall by default, we need to + * modify this function pointer. + */ +typedef unsigned (__stdcall *WinThreadFunc_t)(void *cookie); + +uint32_t IPlatform::CreateThread(IPlatform::ThreadFunc_t func, void* cookie) { + uint32_t id; + /* + * We use our own code here instead of NaClThreadCtor because + * it does not report the thread ID only the handle. + * TODO(noelallen) - Merge port and platform + */ + uintptr_t res = _beginthreadex(NULL, 0, + reinterpret_cast<WinThreadFunc_t>(func), + cookie, 0, &id); + + return id; +} + +void IPlatform::Relinquish(uint32_t msec) { + Sleep(msec); +} + +bool IPlatform::GetMemory(uint64_t virt, uint32_t len, void *dst) { + uint32_t oldFlags = Reprotect(reinterpret_cast<void*>(virt), + len, PAGE_READONLY); + + if (oldFlags == -1) return false; + + memcpy(dst, reinterpret_cast<void*>(virt), len); + (void) Reprotect(reinterpret_cast<void*>(virt), len, oldFlags); + return true; +} + +bool IPlatform::SetMemory(uint64_t virt, uint32_t len, void *src) { + uint32_t oldFlags = Reprotect(reinterpret_cast<void*>(virt), + len, PAGE_EXECUTE_READWRITE); + + if (oldFlags == -1) return false; + + memcpy(reinterpret_cast<void*>(virt), src, len); + FlushInstructionCache(GetCurrentProcess(), + reinterpret_cast<void*>(virt), len); + (void) Reprotect(reinterpret_cast<void*>(virt), len, oldFlags); + return true; +} + +} // End of port namespace + diff --git a/native_client_sdk/src/build_tools/debug_server/debug_stub/win/thread_impl.cc b/native_client_sdk/src/build_tools/debug_server/debug_stub/win/thread_impl.cc new file mode 100644 index 0000000..6998386 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/debug_stub/win/thread_impl.cc @@ -0,0 +1,438 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <assert.h> +#include <windows.h> +#include <exception> +#include <stdexcept> + +#include "native_client/src/debug_server/port/mutex.h" +#include "native_client/src/debug_server/port/thread.h" + +/* + * Define the OS specific portions of gdb_utils IThread interface. + */ + +#define AAA(x) case x: return #x + +//0x40010006 + + const char* ExceptionCodeStr(int code) { + switch(code) { + AAA(EXCEPTION_ACCESS_VIOLATION); + AAA(EXCEPTION_ARRAY_BOUNDS_EXCEEDED); + AAA(EXCEPTION_BREAKPOINT); + AAA(EXCEPTION_DATATYPE_MISALIGNMENT); + AAA(EXCEPTION_FLT_DENORMAL_OPERAND); + AAA(EXCEPTION_FLT_DIVIDE_BY_ZERO); + AAA(EXCEPTION_FLT_INEXACT_RESULT); + AAA(EXCEPTION_FLT_INVALID_OPERATION); + AAA(EXCEPTION_FLT_OVERFLOW); + AAA(EXCEPTION_FLT_STACK_CHECK); + AAA(EXCEPTION_FLT_UNDERFLOW); + AAA(EXCEPTION_ILLEGAL_INSTRUCTION); + AAA(EXCEPTION_IN_PAGE_ERROR); + AAA(EXCEPTION_INT_DIVIDE_BY_ZERO); + AAA(EXCEPTION_INT_OVERFLOW); + AAA(EXCEPTION_INVALID_DISPOSITION); + AAA(EXCEPTION_NONCONTINUABLE_EXCEPTION); + AAA(EXCEPTION_PRIV_INSTRUCTION); + AAA(EXCEPTION_SINGLE_STEP); + AAA(EXCEPTION_STACK_OVERFLOW); + } + return "N/A"; +} + +namespace port { + +static IThread::CatchFunc_t s_CatchFunc = NULL; +static void* s_CatchCookie = NULL; +static PVOID s_OldCatch = NULL; + +enum PosixSignals { + SIGINT = 2, + SIGQUIT = 3, + SIGILL = 4, + SIGTRACE= 5, + SIGBUS = 7, + SIGFPE = 8, + SIGKILL = 9, + SIGSEGV = 11, + SIGSTKFLT = 16, +}; + + +static IMutex* ThreadGetLock() { + static IMutex* mutex_ = IMutex::Allocate(); + return mutex_; +} + +static IThread::ThreadMap_t *ThreadGetMap() { + static IThread::ThreadMap_t* map_ = new IThread::ThreadMap_t; + return map_; +} + +static int8_t ExceptionToSignal(int ex) { + switch (ex) { + case EXCEPTION_GUARD_PAGE: + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + case EXCEPTION_DATATYPE_MISALIGNMENT: + case EXCEPTION_ACCESS_VIOLATION: + case EXCEPTION_IN_PAGE_ERROR: + return SIGSEGV; + + case EXCEPTION_BREAKPOINT: + case EXCEPTION_SINGLE_STEP: + return SIGTRACE; + + case EXCEPTION_FLT_DENORMAL_OPERAND: + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + case EXCEPTION_FLT_INEXACT_RESULT: + case EXCEPTION_FLT_INVALID_OPERATION: + case EXCEPTION_FLT_OVERFLOW: + case EXCEPTION_FLT_STACK_CHECK: + case EXCEPTION_FLT_UNDERFLOW: + return SIGFPE; + + case EXCEPTION_INT_DIVIDE_BY_ZERO: + case EXCEPTION_INT_OVERFLOW: + case EXCEPTION_ILLEGAL_INSTRUCTION: + case EXCEPTION_PRIV_INSTRUCTION: + return SIGILL; + + case EXCEPTION_STACK_OVERFLOW: + return SIGSTKFLT; + + case CONTROL_C_EXIT: + return SIGQUIT; + + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + case EXCEPTION_INVALID_DISPOSITION: + case EXCEPTION_INVALID_HANDLE: + return SIGILL; + } + return SIGILL; +} + + +#ifdef _WIN64 +static void *GetPointerToRegInCtx(CONTEXT *ctx, int32_t num) { + switch (num) { + case 0: return &ctx->Rax; + case 1: return &ctx->Rbx; + case 2: return &ctx->Rcx; + case 3: return &ctx->Rdx; + case 4: return &ctx->Rsi; + case 5: return &ctx->Rdi; + case 6: return &ctx->Rbp; + case 7: return &ctx->Rsp; + case 8: return &ctx->R8; + case 9: return &ctx->R9; + case 10:return &ctx->R10; + case 11:return &ctx->R11; + case 12:return &ctx->R12; + case 13:return &ctx->R13; + case 14:return &ctx->R14; + case 15:return &ctx->R15; + case 16:return &ctx->Rip; + case 17:return &ctx->EFlags; + case 18:return &ctx->SegCs; + case 19:return &ctx->SegSs; + case 20:return &ctx->SegDs; + case 21:return &ctx->SegEs; + case 22:return &ctx->SegFs; + case 23:return &ctx->SegGs; + } + + throw std::out_of_range("Register index out of range."); +} + +static int GetSizeofRegInCtx(int32_t num) { + CONTEXT *ctx = NULL; + switch (num) { + case 0: return sizeof ctx->Rax; + case 1: return sizeof ctx->Rbx; + case 2: return sizeof ctx->Rcx; + case 3: return sizeof ctx->Rdx; + case 4: return sizeof ctx->Rsi; + case 5: return sizeof ctx->Rdi; + case 6: return sizeof ctx->Rbp; + case 7: return sizeof ctx->Rsp; + case 8: return sizeof ctx->R8; + case 9: return sizeof ctx->R9; + case 10:return sizeof ctx->R10; + case 11:return sizeof ctx->R11; + case 12:return sizeof ctx->R12; + case 13:return sizeof ctx->R13; + case 14:return sizeof ctx->R14; + case 15:return sizeof ctx->R15; + case 16:return sizeof ctx->Rip; + case 17:return sizeof ctx->EFlags; + case 18:return sizeof ctx->SegCs; + case 19:return sizeof ctx->SegSs; + case 20:return sizeof ctx->SegDs; + case 21:return sizeof ctx->SegEs; + case 22:return sizeof ctx->SegFs; + case 23:return sizeof ctx->SegGs; + } + + throw std::out_of_range("Register index out of range."); +} + +#else + +static void *GetPointerToRegInCtx(CONTEXT *ctx, int32_t num) { + switch (num) { + case 0: return &ctx->Eax; + case 1: return &ctx->Ecx; + case 2: return &ctx->Edx; + case 3: return &ctx->Ebx; + case 4: return &ctx->Ebp; + case 5: return &ctx->Esp; + case 6: return &ctx->Esi; + case 7: return &ctx->Edi; + case 8: return &ctx->Eip; + case 9: return &ctx->EFlags; + case 10:return &ctx->SegCs; + case 11:return &ctx->SegSs; + case 12:return &ctx->SegDs; + case 13:return &ctx->SegEs; + case 14:return &ctx->SegFs; + case 15:return &ctx->SegGs; + } + + throw std::out_of_range("Register index out of range."); +} + +static int GetSizeofRegInCtx(int32_t num) { + CONTEXT *ctx = NULL; + switch (num) { + case 0: return sizeof ctx->Eax; + case 1: return sizeof ctx->Ecx; + case 2: return sizeof ctx->Edx; + case 3: return sizeof ctx->Ebx; + case 4: return sizeof ctx->Ebp; + case 5: return sizeof ctx->Esp; + case 6: return sizeof ctx->Esi; + case 7: return sizeof ctx->Edi; + case 8: return sizeof ctx->Eip; + case 9: return sizeof ctx->EFlags; + case 10:return sizeof ctx->SegCs; + case 11:return sizeof ctx->SegSs; + case 12:return sizeof ctx->SegDs; + case 13:return sizeof ctx->SegEs; + case 14:return sizeof ctx->SegFs; + case 15:return sizeof ctx->SegGs; + } + + throw std::out_of_range("Register index out of range."); +} +#endif + + +class Thread : public IThread { + public: + explicit Thread(uint32_t id) : ref_(1), id_(id), + handle_(NULL), state_(RUNNING) { + handle_ = OpenThread(THREAD_ALL_ACCESS, false, id); + memset(&context_, 0, sizeof(context_)); + context_.ContextFlags = CONTEXT_ALL; + if (NULL == handle_) state_ = DEAD; + } + + ~Thread() { + if (NULL == handle_) return; + + // This should always succeed, so ignore the return. + (void) CloseHandle(handle_); + } + + uint32_t GetId() { + return id_; + } + + State GetState() { + return state_; + } + + virtual bool Suspend() { + MutexLock lock(ThreadGetLock()); + if (state_ != RUNNING) return false; + + // Attempt to suspend the thread + DWORD count = SuspendThread(handle_); + + // Ignore result, since there is nothing we can do about + // it at this point. + (void) GetThreadContext(handle_, &context_); + + if (count != -1) { + state_ = SUSPENDED; + return true; + } + + return false; + } + + virtual bool Resume() { + MutexLock lock(ThreadGetLock()); + if (state_ != SUSPENDED) return false; + + // Ignore result, since there is nothing we can do about + // it at this point if the set fails. + (void) SetThreadContext(handle_, &context_); + + // Attempt to resume the thread + if (ResumeThread(handle_) != -1) { + state_ = RUNNING; + return true; + } + + return false; + } + + #define TRAP_FLAG (1 << 8) + virtual bool SetStep(bool on) { + if ((state_ == RUNNING) || (state_ == DEAD)) return false; + + if (on) { + context_.EFlags |= TRAP_FLAG; + } else { + context_.EFlags &= ~TRAP_FLAG; + } + return true; + } + + virtual bool GetRegister(uint32_t index, void *dst, uint32_t len) { + uint32_t clen = GetSizeofRegInCtx(index); + void* src = GetPointerToRegInCtx(&context_, index); + + if ((state_ == RUNNING) || (state_ == DEAD)) return false; + + // TODO(noelallen) we assume big endian + if (clen < len) len = clen; + memcpy(dst, src, len); + + return true; + } + + virtual bool SetRegister(uint32_t index, void* src, uint32_t len) { + uint32_t clen = GetSizeofRegInCtx(index); + void* dst = GetPointerToRegInCtx(&context_, index); + + if ((state_ == RUNNING) || (state_ == DEAD) || + (state_ != SYSCALL)) return false; + + // TODO(noelallen) we assume big endian + if (clen < len) len = clen; + memcpy(dst, src, len); + + return true; + } + + virtual void* GetContext() { return &context_; } + + //swi_lsp: non-browser app; disable + + static LONG NTAPI ExceptionCatch(PEXCEPTION_POINTERS ep) { +/* + printf("---> ExceptionCatch(PEXCEPTION_POINTERS: addr=%p code=%d=0x%X=%s [%d-%s]\n", + ep->ExceptionRecord->ExceptionAddress, + ep->ExceptionRecord->ExceptionCode, + ep->ExceptionRecord->ExceptionCode, + ExceptionCodeStr(ep->ExceptionRecord->ExceptionCode), ep->ExceptionRecord->NumberParameters, + ep->ExceptionRecord->ExceptionInformation[1]); +*/ + if(DBG_PRINTEXCEPTION_C == ep->ExceptionRecord->ExceptionCode) + return EXCEPTION_CONTINUE_EXECUTION; + + uint32_t id = static_cast<uint32_t>(GetCurrentThreadId()); + Thread* thread = static_cast<Thread*>(Acquire(id)); + + // If we are not tracking this thread, then ignore it + if (NULL == thread) return EXCEPTION_CONTINUE_SEARCH; + + State old_state = thread->state_; + thread->state_ = SIGNALED; + int8_t sig = ExceptionToSignal(ep->ExceptionRecord->ExceptionCode); + + void *ctx = thread->GetContext(); + + memcpy(ctx, ep->ContextRecord, sizeof(CONTEXT)); + if (NULL != s_CatchFunc) s_CatchFunc(id, sig, s_CatchCookie); + memcpy(ep->ContextRecord, ctx, sizeof(CONTEXT)); + + thread->state_ = old_state; + Release(thread); + return EXCEPTION_CONTINUE_EXECUTION; + } + + + + private: + uint32_t ref_; + uint32_t id_; + State state_; + HANDLE handle_; + CONTEXT context_; + + friend class IThread; +}; + +IThread* IThread::Acquire(uint32_t id, bool create) { + MutexLock lock(ThreadGetLock()); + Thread* thread; + ThreadMap_t &map = *ThreadGetMap(); + + // Check if we have that thread + if (map.count(id)) { + thread = static_cast<Thread*>(map[id]); + thread->ref_++; + return thread; + } + + // If not, can we create it? + if (create) { + // If not add it to the map + thread = new Thread(id); + if (NULL == thread->handle_) { + delete thread; + return NULL; + } + + map[id] = thread; + return thread; + } + + return NULL; +} + +void IThread::Release(IThread *ithread) { + MutexLock lock(ThreadGetLock()); + Thread* thread = static_cast<Thread*>(ithread); + thread->ref_--; + + if (thread->ref_ == 0) { + ThreadGetMap()->erase(thread->id_); + delete static_cast<IThread*>(thread); + } +} + +void IThread::SetExceptionCatch(IThread::CatchFunc_t func, void *cookie) { + MutexLock lock(ThreadGetLock()); + + // Remove our old catch if there is one, this allows us to add again + if (NULL != s_OldCatch) RemoveVectoredExceptionHandler(s_OldCatch); + + // Add the new one, at the front of the list + s_OldCatch = AddVectoredExceptionHandler(1, Thread::ExceptionCatch); + s_CatchFunc = func; + s_CatchCookie = cookie; +} + + +} // End of port namespace + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/abi.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/abi.cc new file mode 100644 index 0000000..b1bbd04 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/abi.cc @@ -0,0 +1,209 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <assert.h> + +#include <map> +#include <string> + +#include "native_client/src/debug_server/gdb_rsp/abi.h" +#include "native_client/src/debug_server/port/platform.h" + +using port::IPlatform; + +namespace gdb_rsp { + +#define MINIDEF(x, name, purpose) { #name, sizeof(x), Abi::purpose, 0, 0 } +#define BPDEF(x) { sizeof(x), x } + +static Abi::RegDef RegsX86_64[] = { + MINIDEF(uint64_t, rax, GENERAL), + MINIDEF(uint64_t, rbx, GENERAL), + MINIDEF(uint64_t, rcx, GENERAL), + MINIDEF(uint64_t, rdx, GENERAL), + MINIDEF(uint64_t, rsi, GENERAL), + MINIDEF(uint64_t, rdi, GENERAL), + MINIDEF(uint64_t, rbp, GENERAL), + MINIDEF(uint64_t, rsp, GENERAL), + MINIDEF(uint64_t, r8, GENERAL), + MINIDEF(uint64_t, r9, GENERAL), + MINIDEF(uint64_t, r10, GENERAL), + MINIDEF(uint64_t, r11, GENERAL), + MINIDEF(uint64_t, r12, GENERAL), + MINIDEF(uint64_t, r13, GENERAL), + MINIDEF(uint64_t, r14, GENERAL), + MINIDEF(uint64_t, r15, GENERAL), + MINIDEF(uint64_t, rip, INST_PTR), + MINIDEF(uint32_t, eflags, FLAGS), + MINIDEF(uint32_t, cs, SEGMENT), + MINIDEF(uint32_t, ss, SEGMENT), + MINIDEF(uint32_t, ds, SEGMENT), + MINIDEF(uint32_t, es, SEGMENT), + MINIDEF(uint32_t, fs, SEGMENT), + MINIDEF(uint32_t, gs, SEGMENT), +}; + +static Abi::RegDef RegsX86_32[] = { + MINIDEF(uint32_t, eax, GENERAL), + MINIDEF(uint32_t, ecx, GENERAL), + MINIDEF(uint32_t, edx, GENERAL), + MINIDEF(uint32_t, ebx, GENERAL), + MINIDEF(uint32_t, esp, GENERAL), + MINIDEF(uint32_t, ebp, GENERAL), + MINIDEF(uint32_t, esi, GENERAL), + MINIDEF(uint32_t, edi, GENERAL), + MINIDEF(uint32_t, eip, INST_PTR), + MINIDEF(uint32_t, eflags, FLAGS), + MINIDEF(uint32_t, cs, SEGMENT), + MINIDEF(uint32_t, ss, SEGMENT), + MINIDEF(uint32_t, ds, SEGMENT), + MINIDEF(uint32_t, es, SEGMENT), + MINIDEF(uint32_t, fs, SEGMENT), + MINIDEF(uint32_t, gs, SEGMENT), +}; + +static Abi::RegDef RegsArm[] = { + MINIDEF(uint32_t, r0, GENERAL), + MINIDEF(uint32_t, r1, GENERAL), + MINIDEF(uint32_t, r2, GENERAL), + MINIDEF(uint32_t, r3, GENERAL), + MINIDEF(uint32_t, r4, GENERAL), + MINIDEF(uint32_t, r5, GENERAL), + MINIDEF(uint32_t, r6, GENERAL), + MINIDEF(uint32_t, r7, GENERAL), + MINIDEF(uint32_t, r8, GENERAL), + MINIDEF(uint32_t, r9, GENERAL), + MINIDEF(uint32_t, r10, GENERAL), + MINIDEF(uint32_t, r11, GENERAL), + MINIDEF(uint32_t, r12, GENERAL), + MINIDEF(uint32_t, sp, STACK_PTR), + MINIDEF(uint32_t, lr, LINK_PTR), + MINIDEF(uint32_t, pc, INST_PTR), +}; + +static uint8_t BPCodeX86[] = { 0xCC }; + +static Abi::BPDef BPX86 = BPDEF(BPCodeX86); + +static AbiMap_t s_Abis; + +// AbiInit & AbiIsAvailable +// This pair of functions work together as singleton to +// ensure the module has been correctly initialized. All +// dependant functions should call AbiIsAvailable to ensure +// the module is ready. +static bool AbiInit() { + Abi::Register("i386", RegsX86_32, sizeof(RegsX86_32), &BPX86); + Abi::Register("i386:x86-64", RegsX86_64, sizeof(RegsX86_64), &BPX86); + + // TODO(cbiffle) Figure out how to REALLY detect ARM, and define Breakpoint + Abi::Register("iwmmxt", RegsArm, sizeof(RegsArm), NULL); + + return true; +} + +static bool AbiIsAvailable() { + static bool initialized_ = AbiInit(); + return initialized_; +} + + + +Abi::Abi() {} +Abi::~Abi() {} + +void Abi::Register(const char *name, RegDef *regs, + uint32_t bytes, const BPDef *bp) { + uint32_t offs = 0; + const uint32_t cnt = bytes / sizeof(RegDef); + + // Build indexes and offsets + for (uint32_t loop = 0; loop < cnt; loop++) { + regs[loop].index_ = loop; + regs[loop].offset_ = offs; + offs += regs[loop].bytes_; + } + + Abi *abi = new Abi; + + abi->name_ = name; + abi->regCnt_ = cnt; + abi->regDefs_= regs; + abi->ctxSize_ = offs; + abi->bpDef_ = bp; + + s_Abis[name] = abi; +} + +const Abi* Abi::Find(const char *name) { + if (!AbiIsAvailable()) { + IPlatform::LogError("Failed to initalize ABIs."); + return NULL; + } + + AbiMap_t::const_iterator itr = s_Abis.find(name); + if (itr == s_Abis.end()) return NULL; + + return itr->second; +} + +const Abi* Abi::Get() { + static const Abi* abi = NULL; + + if ((NULL == abi) && AbiIsAvailable()) { +#ifdef GDB_RSP_ABI_ARM + abi = Abi::Find("iwmmxt"); +#elif GDB_RSP_ABI_X86_64 + abi = Abi::Find("i386:x86-64"); +#elif GDB_RSP_ABI_X86 + abi = Abi::Find("i386"); +#else +#error "Unknown CPU architecture." +#endif + } + + return abi; +} + +const char* Abi::GetName() const { + return name_; +} + +const Abi::BPDef *Abi::GetBreakpointDef() const { + return bpDef_; +} + +uint32_t Abi::GetContextSize() const { + return ctxSize_; +} + +uint32_t Abi::GetRegisterCount() const { + return regCnt_; +} + +const Abi::RegDef *Abi::GetRegisterDef(uint32_t index) const { + if (index >= regCnt_) return NULL; + + return ®Defs_[index]; +} + +const Abi::RegDef *Abi::GetRegisterType(RegType rtype, uint32_t nth) const { + uint32_t typeNum = 0; + + // Scan for the "nth" register of rtype; + for (uint32_t regNum = 0; regNum < regCnt_; regNum++) { + if (rtype == regDefs_[regNum].type_) { + if (typeNum == nth) return ®Defs_[regNum]; + typeNum++; + } + } + + // Otherwise we failed to find it + return NULL; +} + +} // namespace gdb_rsp + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/abi.h b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/abi.h new file mode 100644 index 0000000..d360274 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/abi.h @@ -0,0 +1,114 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +// This module provides an interface for performing ABI specific +// functions needed by the Target and Host. ABI objects are provided +// to the rest of the system as "const" to prevent accidental modification. +// None of the resources in the Abi object are actually owned by the Abi +// object, they are assumed to be static and never destroyed. +// +// This module will only throw standard errors +// std::bad_alloc - when out of memory +// std::out_of_range - when using an out of range regsiter index +// +// It is required that Init be called prior to calling Find to ensure +// the various built-in ABIs have been registered. + +#ifndef NATIVE_CLIENT_GDB_RSP_ABI_H_ +#define NATIVE_CLIENT_GDB_RSP_ABI_H_ 1 + +#include <map> +#include <string> + +#include "native_client/src/debug_server/port/std_types.h" + +namespace gdb_rsp { + +class Abi { + public: + enum RegType { + GENERAL, + LINK_PTR, + INST_PTR, + STACK_PTR, + FRAME_PTR, + BASE_PTR, + SEGMENT, + TLS, + FLAGS, + CONTROL, + REG_TYPE_CNT + }; + + // Defines an individual register + struct RegDef { + const char *name_; + uint32_t bytes_; + RegType type_; + uint32_t index_; + uint32_t offset_; + }; + + // Defines how breakpoints work. + // code_ points to a series of bytes which will be placed in the code to + // create the breakpoint. size_ is the size of that array. We use a 32b + // size since the memory modification API only supports a 32b size. + struct BPDef { + uint32_t size_; + uint8_t *code_; + }; + + // Returns the registered name of this ABI. + const char *GetName() const; + + // Returns a pointer to the breakpoint definition. + const BPDef *GetBreakpointDef() const; + + // Returns the size of the thread context. + uint32_t GetContextSize() const; + + // Returns the number of registers visbible. + uint32_t GetRegisterCount() const; + + // Returns a definition of the register at the provided index, or + // NULL if it does not exist. + const RegDef *GetRegisterDef(uint32_t index) const; + + // Returns the 'nth' register of the type specified or NULL if it + // does not exist. + const RegDef *GetRegisterType(RegType rt, uint32_t index = 0) const; + + // Called to assign a set of register definitions to an ABI. + // This function is non-reentrant. + static void Register(const char *name, RegDef *defs, + uint32_t cnt, const BPDef *bp); + + // Called to search the map for a matching Abi by name. + // This function is reentrant. + static const Abi *Find(const char *name); + + // Get the ABI of for the running application. + static const Abi *Get(); + + protected: + const char *name_; + const RegDef *regDefs_; + uint32_t regCnt_; + uint32_t ctxSize_; + const BPDef *bpDef_; + + private: + Abi(); + ~Abi(); + void operator =(const Abi&); +}; + +typedef std::map<std::string, Abi*> AbiMap_t; + +} // namespace gdb_rsp + +#endif // NATIVE_CLIENT_GDB_RSP_ABI_H_ + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/abi_test.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/abi_test.cc new file mode 100644 index 0000000..6f966b5 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/abi_test.cc @@ -0,0 +1,140 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "native_client/src/debug_server/gdb_rsp/abi.h" + +using gdb_rsp::Abi; + +int VerifyAbi(const char *name, uint32_t regs) { + int errs = 0; + + const Abi *abi = Abi::Find(name); + if (NULL != abi) { + uint32_t regCnt = abi->GetRegisterCount(); + uint32_t byteCnt= abi->GetContextSize(); + uint32_t bytes = 0; + uint32_t loop = 0; + + if (strcmp(abi->GetName(), name)) { + printf("Incorrect name for ABI %s.\n", name); + errs++; + } + + if (regCnt != regs) { + printf("Incorrect number of registers for ABI %s.\n", name); + errs++; + } + + if (abi->GetRegisterDef(regs) != NULL) { + printf("Unexpected register for ABI %s.\n", name); + errs++; + } + + for (loop = 0; loop < regs; loop++) { + const Abi::RegDef *def = abi->GetRegisterDef(loop); + if (NULL == def) { + printf("Missing register for ABI %s, reg %d.\n", name, loop); + errs++; + break; + } + + if (NULL == def->name_) { + printf("Missing register name for ABI %s, reg %d.\n", name, loop); + errs++; + break; + } + + if (loop != def->index_) { + printf("Index mismatch for ABI %s, reg %d.\n", name, loop); + errs++; + break; + } + + if ((1 > def->bytes_) || (def->bytes_ > byteCnt)) { + printf("Index mismatch for ABI %s, reg %d.\n", name, loop); + errs++; + break; + } + + if ((def->type_ < Abi::GENERAL) || (def->type_ >= Abi::REG_TYPE_CNT)) { + printf("Illegal register type ABI %s, reg %d.\n", name, loop); + errs++; + break; + } + + if (def->offset_ != bytes) { + printf("Offset mismatch in ABI %s, reg %d.\n", name, loop); + errs++; + break; + } + + bytes += def->bytes_; + } + + if (bytes != byteCnt) { + printf("Context size mismatch for ABI %s.\n", name); + errs++; + } + + if (abi->GetRegisterType(Abi::GENERAL) == NULL) { + printf("Missing general registers for ABI %s.\n", name); + errs++; + } + + if (abi->GetRegisterType(Abi::INST_PTR) == NULL) { + printf("Missing instruction pointer for ABI %s.\n", name); + errs++; + } + } else { + printf("Could not find ABI %s.\n", name); + errs++; + } + return errs; +} + +int TestAbi() { + int errs = 0; + + // TODO(cbiffle) Figure out how to REALLY detect ARM + errs += VerifyAbi("iwmmxt", 16); + errs += VerifyAbi("i386", 16); + errs += VerifyAbi("i386:x86-64", 24); + + // Get the default ABI + const Abi* abi = Abi::Get(); + if (NULL == abi) { + printf("Failed to get default ABI.\n"); + errs++; + } + + // Get a generic register + const Abi::RegDef *def = abi->GetRegisterType(Abi::GENERAL); + if (NULL == def) { + printf("Failed to get a generic register on the default ABI %s.\n", + abi->GetName()); + errs++; + } else { + if (def->bytes_ != sizeof(intptr_t)) { + printf("Generic register %d != %d pointer size for %s\n", + static_cast<int>(def->bytes_), + static_cast<int>(sizeof(intptr_t)), + abi->GetName()); + errs++; + } + } + + if (NULL != Abi::Find("non-existant")) { + printf("Found 'non-existant' ABI.\n"); + errs++; + } + + return errs; +} + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/build.scons b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/build.scons new file mode 100644 index 0000000..3303c13 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/build.scons @@ -0,0 +1,57 @@ +# -*- python -*- +# Copyright 2010 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can +# be found in the LICENSE file. + +# This file needs to be in sync with $SOURCE_ROOT/ppapi/ppapi.gyp +# at the revision specified in $SOURCE_ROOT/native_client/DEPS. + +Import('env') + +# this is needed for including gdb_utils files +env.Append(CPPPATH=['${SOURCE_ROOT}/gdb_utils/src']) + +if env.Bit('windows'): + env.Append(CPPDEFINES=['WIN32']) + if env.Bit('target_x86_64'): + env.Append(CPPDEFINES=['WIN64']) + +if env.Bit('target_arm'): + env.Append(CPPDEFINES=['GDB_RSP_ABI_ARM']) +elif env.Bit('target_x86'): + env.Append(CPPDEFINES=['GDB_RSP_ABI_X86']) + if env.Bit('target_x86_64'): + env.Append(CPPDEFINES=['GDB_RSP_ABI_X86_64']) +else: + raise Exception("Unknown target") + +rsp_sources = [ + 'abi.cc', + 'host.cc', + 'packet.cc', + 'session.cc', + 'target.cc', + 'util.cc', + ] + +rsp_test_sources = [ + 'abi_test.cc', + 'host_test.cc', + 'packet_test.cc', + 'session_test.cc', + 'session_mock.cc', + 'target_test.cc', + 'util_test.cc', + 'test.cc' + ] + +# Build only for Win64 +env.DualLibrary('gdb_rsp', rsp_sources) +gdb_rsp_test_exe = env.ComponentProgram('gdb_rsp_unittest', + rsp_test_sources, + EXTRA_LIBS=['gdb_rsp']) + +node = env.CommandTest( + 'gdb_rsp_unittest.out', + command=[gdb_rsp_test_exe]) +env.AddNodeToTestSuite(node, ['small_tests'], 'run_gdb_rsp_tests') diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp.gyp b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp.gyp new file mode 100644 index 0000000..e634300 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp.gyp @@ -0,0 +1,162 @@ +# -*- python -*- +# Copyright 2009, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +{ + 'includes': [ + '../../../build/common.gypi', + ], + 'target_defaults': { + 'conditions': [ + ['target_arch=="ia32"', { + 'defines': [ + 'GDB_RSP_ABI_X86', + ], + }], + ['target_arch=="x64"', { + 'defines': [ + 'GDB_RSP_ABI_X86', + 'GDB_RSP_ABI_X86_64', + ], + }], + ['target_arch=="arm"', { + 'defines': [ + 'GDB_RSP_ABI_ARM', + ], + }], + ], + 'target_conditions': [ + ['OS=="linux" or OS=="mac"', { + 'cflags': [ + '-fexceptions', + ], + 'cflags_cc' : [ + '-frtti', + ] + }], + ['OS=="mac"', { + 'xcode_settings': { + 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', # -fexceptions + 'GCC_ENABLE_CPP_RTTI': 'YES', # -frtti + } + }], + ['OS=="win"', { + 'msvs_settings': { + 'VCCLCompilerTool': { + 'ExceptionHandling': '1', + 'RuntimeTypeInfo': 'true', + }, + } + }], + ], + }, + 'variables': { + 'gdb_rsp_sources': [ + 'abi.cc', + 'abi.h', + 'host.cc', + 'host.h', + 'packet.cc', + 'packet.h', + 'session.cc', + 'session.h', + 'target.cc', + 'target.h', + 'util.cc', + 'util.h', + ], + 'gdb_test_sources': [ + 'abi_test.cc', + 'packet_test.cc', + 'host_test.cc', + 'session_mock.h', + 'session_mock.cc', + 'session_test.cc', + 'target_test.cc', + 'util_test.cc', + 'test.cc', + 'test.h', + ], + }, + 'targets': [ + { + 'target_name': 'gdb_rsp', + 'type': 'static_library', + 'sources': [ + '<@(gdb_rsp_sources)', + ], + }, + { + 'target_name': 'gdb_rsp_test', + 'type': 'executable', + 'sources': [ + '<@(gdb_test_sources)', + ], + 'dependencies': [ + 'gdb_rsp', + ] + }, + ], + 'conditions': [ + ['OS=="win"', { + 'targets': [ + { + 'target_name': 'gdb_rsp64', + 'type': 'static_library', + 'sources': [ + '<@(gdb_rsp_sources)', + ], + 'defines': [ + 'GDB_RSP_ABI_X86_64', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + }, + { + 'target_name': 'gdb_rsp_test64', + 'type': 'executable', + 'sources': [ + '<@(gdb_test_sources)', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + 'dependencies': [ + 'gdb_rsp64', + ] + }, + ], + }], + ], +} diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp.rules b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp.rules new file mode 100644 index 0000000..bc56170 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp.rules @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioToolFile Name="gdb_rsp" Version="8.00"> + <Rules> + <CustomBuildRule AdditionalDependencies="..\..\..\..\native_client\tools\win_as.py;$(InputPath)" CommandLine="..\..\..\..\native_client\tools\win_py.cmd ..\..\..\..\native_client\tools\win_as.py -a $(PlatformName) -o $(IntDir)\$(InputName).obj -p ..\..\..\.. $(InputPath)" ExecutionDescription="Building assembly language file $(InputPath)" FileExtensions="S" Name="assembler (gnu-compatible)" Outputs="$(IntDir)\$(InputName).obj"/> + </Rules> +</VisualStudioToolFile> diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp64.rules b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp64.rules new file mode 100644 index 0000000..79d04ca --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp64.rules @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioToolFile Name="gdb_rsp64" Version="8.00"> + <Rules> + <CustomBuildRule AdditionalDependencies="..\..\..\..\native_client\tools\win_as.py;$(InputPath)" CommandLine="..\..\..\..\native_client\tools\win_py.cmd ..\..\..\..\native_client\tools\win_as.py -a $(PlatformName) -o $(IntDir)\$(InputName).obj -p ..\..\..\.. $(InputPath)" ExecutionDescription="Building assembly language file $(InputPath)" FileExtensions="S" Name="assembler (gnu-compatible)" Outputs="$(IntDir)\$(InputName).obj"/> + </Rules> +</VisualStudioToolFile> diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp_test.rules b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp_test.rules new file mode 100644 index 0000000..6a3ddbdd3 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp_test.rules @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioToolFile Name="gdb_rsp_test" Version="8.00"> + <Rules> + <CustomBuildRule AdditionalDependencies="..\..\..\..\native_client\tools\win_as.py;$(InputPath)" CommandLine="..\..\..\..\native_client\tools\win_py.cmd ..\..\..\..\native_client\tools\win_as.py -a $(PlatformName) -o $(IntDir)\$(InputName).obj -p ..\..\..\.. $(InputPath)" ExecutionDescription="Building assembly language file $(InputPath)" FileExtensions="S" Name="assembler (gnu-compatible)" Outputs="$(IntDir)\$(InputName).obj"/> + </Rules> +</VisualStudioToolFile> diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp_test64.rules b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp_test64.rules new file mode 100644 index 0000000..f63bc2e --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/gdb_rsp_test64.rules @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioToolFile Name="gdb_rsp_test64" Version="8.00"> + <Rules> + <CustomBuildRule AdditionalDependencies="..\..\..\..\native_client\tools\win_as.py;$(InputPath)" CommandLine="..\..\..\..\native_client\tools\win_py.cmd ..\..\..\..\native_client\tools\win_as.py -a $(PlatformName) -o $(IntDir)\$(InputName).obj -p ..\..\..\.. $(InputPath)" ExecutionDescription="Building assembly language file $(InputPath)" FileExtensions="S" Name="assembler (gnu-compatible)" Outputs="$(IntDir)\$(InputName).obj"/> + </Rules> +</VisualStudioToolFile> diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/host.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/host.cc new file mode 100644 index 0000000..9b00b03 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/host.cc @@ -0,0 +1,524 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "native_client/src/debug_server/gdb_rsp/abi.h" +#include "native_client/src/debug_server/gdb_rsp/host.h" +#include "native_client/src/debug_server/gdb_rsp/packet.h" +#include "native_client/src/debug_server/gdb_rsp/session.h" +#include "native_client/src/debug_server/gdb_rsp/util.h" + +#include "native_client/src/debug_server/port/std_types.h" +#include "native_client/src/debug_server/port/platform.h" + +#ifdef WIN32 +#define snprintf sprintf_s +#endif + + +using std::string; +using port::IPlatform; + +namespace gdb_rsp { + +Host::Thread::Thread(const Abi *abi, uint32_t id) + : id_(id), ctx_(NULL), abi_(abi) { + ctx_ = new uint8_t[abi_->GetContextSize()]; + assert(NULL != ctx_); +} + +Host::Thread::~Thread() { + delete[] ctx_; +} + +uint32_t Host::Thread::GetId() const { + return id_; +} + +const Abi *Host::Thread::GetAbi() const { + return abi_; +} + +void Host::Thread::GetRegister(uint32_t index, void *dst) const { + assert(NULL != dst); + assert(index < abi_->GetRegisterCount()); + + const Abi::RegDef *def = abi_->GetRegisterDef(index); + uint8_t *ptr = ctx_ + def->offset_; + memcpy(dst, ptr, def->bytes_); +} + +void Host::Thread::SetRegister(uint32_t index, const void *src) { + assert(src); + assert(index < abi_->GetRegisterCount()); + + const Abi::RegDef *def = abi_->GetRegisterDef(index); + uint8_t *ptr = ctx_ + def->offset_; + memcpy(ptr, src, def->bytes_); +} + + +// Construct unitialzied +Host::Host(Session *session) : session_(session), abi_(NULL), + status_(HS_UNINIT) { } + +Host::~Host() { } + +bool Host::Init() { + string reply; + + properties_.clear(); + + // Set the default max packet size. We use the default value + // that GDB appears to like which is 1K - xsum/etc overhead. + properties_["PacketSize"] = "3fa"; + + // Get properties + if (!Request("qSupported", &reply)) return false; + + // Parse the semicolon delimited properties + stringvec tokens = StringSplit(reply.data(), ";"); + for (uint32_t loop = 0; loop < tokens.size(); loop++) { + // Properties are in the form of "Key=Val", "Key+", or "Key-" + string &prop = tokens[loop]; + + // If the form is Key=Val, there should be two components Key and Val + stringvec keyval = StringSplit(prop, "="); + if (keyval.size() == 2) { + properties_[keyval[0]] = keyval[1]; + continue; + } + + // Strip off the + or - by splitting to get the name. + keyval = StringSplit(prop, "+-"); + // Size==1 means we got either XXX, XXX+, or XXX- + if (keyval.size() == 1) { + size_t len = prop.length(); + // In which case the last character must be + or -, or we ignore. + switch (prop[len - 1]) { + case '+' : + properties_[keyval[0]] = "true"; + continue; + case '-' : + properties_[keyval[0]] = "false"; + continue; + } + } + + // Otherwise it was a malformed property + IPlatform::LogError("Returned feature with strange assignment: %s", + prop.data()); + } + + // Must support and use property read method to get CPU type + if (!ReadObject("features", "target.xml", &reply)) return false; + + // Search for start of "architecture" tag + const char *name = strstr(reply.data(), "<architecture>"); + if (NULL != name) { + // Size of "<architecture>" + const int nameStart = 14; + char *str = strdup(&name[nameStart]); + char *term = strchr(str, '<'); + *term = 0; + + abi_ = Abi::Find(str); + free(str); + } + + // Check if we failed to find the correct ABI + if (NULL == abi_) { + IPlatform::LogError("Failed to find ABI for %s\n", reply.data()); + return false; + } + + return Update(); +} + +bool Host::Update() { + ThreadVector_t old_ids; + ThreadVector_t new_ids; + + ThreadMap_t::const_iterator itr = threads_.begin(); + while (itr != threads_.end()) { + old_ids.push_back(itr->first); + itr++; + } + + if (!RequestThreadList(&new_ids)) return false; + + for (uint32_t loop = 0; loop < new_ids.size(); loop++) { + // Large enough for log10(2^32) + NUL + "Hg"; + char tmp[16]; + + uint32_t id = new_ids[loop]; + string request; + string ignore_reply; + + snprintf(tmp, sizeof(tmp), "Hg%x", id); + if (!Request(tmp, &ignore_reply)) { + IPlatform::LogError("Failed to set thread context for %d.\n", id); + continue; + } + + Packet req, resp; + req.AddString("g"); + if (!Send(&req, &resp)) { + IPlatform::LogError("Failed to get thread registers for %d.\n", id); + continue; + } + + Thread *thread = threads_[id]; + if (NULL == thread) { + thread = new Thread(abi_, id); + threads_[id] = thread; + } + + resp.GetBlock(thread->ctx_, abi_->GetContextSize()); + } + + // Update the current state + string reply; + if (Request("?", &reply)) return ParseStopPacket(reply.data()); + + // If we are not "broken" then we must have failed to update + return false; +} + +bool Host::GetThreads(ThreadVector_t* threads) const { + // We can get threads if stopped + if (HS_STOPPED != status_) return false; + + threads->clear(); + ThreadMap_t::const_iterator itr = threads_.begin(); + while (itr != threads_.end()) { + threads->push_back(itr->first); + itr++; + } + + return true; +} + +bool Host::Step() { + Packet out; + + // We can only step if we are stopped + if (HS_STOPPED != status_) return false; + + out.AddRawChar('s'); + if (SendOnly(&out)) { + // We are running again (even if we expect to immediately break) + status_ = HS_RUNNING; + return true; + } + + return false; +} + +bool Host::Continue() { + Packet out; + + // We can only step if we are stopped + if (HS_STOPPED != status_) return false; + + out.AddRawChar('c'); + if (SendOnly(&out)) { + // We are running again + status_ = HS_RUNNING; + return true; + } + + return false; +} + +// Wait to see if we receive a break +bool Host::WaitForBreak() { + // We can not wait if we are not running + if (HS_RUNNING != status_) return false; + + Packet rx; + std::string str; + if (session_->GetPacket(&rx) == false) return false; + + rx.GetString(&str); + if (ParseStopPacket(str.data())) return Update(); + return false; +} + + + +Host::Thread* Host::GetThread(uint32_t id) { + ThreadMap_t::const_iterator itr; + itr = threads_.find(id); + + if (itr == threads_.end()) return NULL; + + return itr->second; +} + +bool Host::ParseStopPacket(const char *data) { + if (strlen(data) < 3) return false; + + // Stop in the form of (S|T|W|X)[XX]{n=xxx,r=yyy;{...}} + // where XX is the result code (Unix signal number for stops) + // or the form of OXX{XX..} where XX is a hex pair encoded string. + switch (data[0]) { + // Both S & T signals are a normal stop + case 'S': + case 'T': + status_ = HS_STOPPED; + break; + + case 'W': + status_ = HS_EXIT; + break; + + case 'X': + status_ = HS_TERMINATED; + break; + + case 'O': + return true; + + default: + return false; + } + + if (!NibblesToByte(&data[1], &lastSignal_)) return false; + + return true; +} + + +bool Host::RequestThreadList(ThreadVector_t* ids) { + string reply; + + ids->clear(); + + if (!Request("qfThreadInfo", &reply)) return false; + + do { + // Check if we are done + if (reply == "l") return true; + + if (reply[0] != 'm') { + IPlatform::LogError("Expecting diffent thread format: %s\n", + reply.data()); + return false; + } + + stringvec replys = StringSplit(&reply[1], ","); + for (uint32_t loop = 0; loop < replys.size(); loop++) { + ids->push_back(strtol(replys[loop].data(), NULL, 16)); + } + } while (Request("qsThreadInfo", &reply)); + + IPlatform::LogError("Failed request of qsThreadInfo.\n", reply.data()); + return false; +} + +bool Host::HasProperty(const char *name) const { + std::map<string, string>::const_iterator itr; + itr = properties_.find(name); + + return properties_.end() != itr; +} + +bool Host::ReadProperty(const char *name, string* val) const { + std::map<string, string>::const_iterator itr; + itr = properties_.find(name); + + if (properties_.end() == itr) return false; + + *val = itr->second; + return true; +} + + +bool Host::ReadObject(const char *type, const char *name, string *reply) { + uint32_t offset = 0; + uint32_t maxTX = 1024; + reply->clear(); + + if (ReadProperty("PacketSize", reply)) { + maxTX = static_cast<uint32_t>(strtol(reply->data(), NULL, 16)); + } + + while (1) { + // We make this 64B to hold 2xlog16(2^64) + NUL + 14 characters although + // in practice these tend to be 16b values. + char tmp[64]; + + string replyPiece; + string query = "qXfer:"; + query += type; + query += ":read:"; + query += name; + snprintf(tmp, sizeof(tmp), ":%x,%x", offset, maxTX); + query += tmp; + + if (!Request(query, &replyPiece)) return false; + + if (replyPiece[0] == 'l') { + *reply += &replyPiece[1]; + return true; + } + + if ((replyPiece[0] == 'E') && (replyPiece.length() == 3)) { + return false; + } + + if (replyPiece[0] != 'm') { + IPlatform::LogError("Expecting more or end signal got:\n\t%s.", + replyPiece.data()); + return false; + } + + *reply += replyPiece; + offset += static_cast<uint32_t>(replyPiece.length()); + } + + return true; +} + +bool Host::Break() { + char brk[2] = { 0x03, 0x00 }; + + // We can only break if running + if (HS_RUNNING != status_) return false; + + status_ = HS_STOPPING; + return RequestOnly(brk); +} + +bool Host::Detach() { + // We can only detach if stopped + if (HS_STOPPED != status_) return false; + + return RequestOnly("d"); +} + +int32_t Host::GetSignal() { + // We always return the lasted cached value + return lastSignal_; +} + +Host::Status Host::GetStatus() { + return status_; +} + +bool Host::GetMemory(void *dst, uint64_t addr, uint32_t size) { + char *ptr = reinterpret_cast<char *>(dst); + uint32_t maxTX = 1024; + string reply; + Packet pktReq, pktReply; + + // We can only access memory if stopped + if (HS_STOPPED != status_) return false; + + if (ReadProperty("PacketSize", &reply)) { + maxTX = strtoul(reply.data(), NULL, 16); + } + + // Reply is encoded as two nibbles plus a single char + // Mxxxxxxxx... + maxTX = (maxTX - 1) / 2; + + while (size) { + uint32_t len = size; + if (len > maxTX) len = maxTX; + + pktReq.Clear(); + pktReq.AddRawChar('m'); + pktReq.AddNumberSep(addr, ','); + pktReq.AddNumberSep(size, 0); + + if (!Send(&pktReq, &pktReply)) return false; + + if (!pktReply.GetBlock(ptr, len)) return false; + + ptr += len; + addr += len; + size -= len; + } + + return true; +} + +bool Host::SetMemory(const void *src, uint64_t addr, uint32_t size) { + const char *ptr = reinterpret_cast<const char *>(src); + uint32_t maxTX = 1024; + string reply; + Packet pktReq, pktReply; + + // We can only access memory if stopped + if (HS_STOPPED != status_) return false; + + if (ReadProperty("PacketSize", &reply)) { + maxTX = strtoul(reply.data(), NULL, 16); + } + + // Reply is encoded as two nibbles plus a single char(3), plus address (16) + // a and size (8) or Maaaaaaaaaaaaaaaa,ssssssss:(27) + maxTX = (maxTX - 27) / 2; + + while (size) { + uint32_t len = size; + if (len > maxTX) len = maxTX; + + pktReq.Clear(); + pktReq.AddRawChar('M'); + pktReq.AddNumberSep(addr, ','); + pktReq.AddNumberSep(len, ':'); + pktReq.AddBlock(src, len); + + if (!Send(&pktReq, &pktReply)) return false; + + ptr += len; + addr += len; + size -= len; + } + + return true; +} + +bool Host::RequestOnly(const string& req) { + Packet pktReq; + + pktReq.AddString(req.data()); + return SendOnly(&pktReq); +} + +bool Host::Request(const string& req, string *resp) { + Packet pktReq, pktResp; + + pktReq.AddString(req.data()); + bool result = Send(&pktReq, &pktResp); + + pktResp.GetString(resp); + + // Check for error code on return + if ((resp->length() == 3) && (resp->data()[0] == 'E')) return false; + + return result; +} + +bool Host::SendOnly(Packet *tx) { + return session_->SendPacketOnly(tx); +} + +bool Host::Send(Packet *tx, Packet *rx) { + if (!session_->SendPacket(tx)) return false; + return session_->GetPacket(rx); +} + +} // namespace gdb_rsp + + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/host.h b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/host.h new file mode 100644 index 0000000..230393c --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/host.h @@ -0,0 +1,149 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +// This module provides an object for handling RSP responsibilities of +// the host side of the connection. The host behaves like a cache, and +// is responsible for syncronization of state between the Target and Host. +// For example, the Host is responsible for updating the thread context +// before restarting the Target, and for updating it's internal array of +// threads whenever the Target stops. + +#ifndef NATIVE_CLIENT_GDB_RSP_HOST_H_ +#define NATIVE_CLIENT_GDB_RSP_HOST_H_ 1 + +#include <map> +#include <string> +#include <vector> + +#include "native_client/src/debug_server/port/std_types.h" + +namespace gdb_rsp { + +class Abi; +class Packet; +class Session; + +class Host { + public: + enum Status { + HS_UNINIT = -1, // Host is uninitialized + HS_RUNNING = 0, // The target is running (no sig since cont.) + HS_STOPPING = 1, // Host has issued a break request + HS_STOPPED = 2, // Host has received a signal and is stopped + HS_EXIT = 3, // Host has received an exit code + HS_TERMINATED = 4 // host has received a termination code + }; + + // The Host::Thread class represents a thread on the Target side, providing + // a cache for its state, which is automatically updated by the parent Host + // object whenever the target starts or stops. + class Thread { + public: + Thread(const Abi *abi, uint32_t id); + ~Thread(); + + public: + uint32_t GetId() const; + const Abi *GetAbi() const; + void GetRegister(uint32_t index, void *dst) const; + void SetRegister(uint32_t index, const void *src); + + private: + uint32_t id_; + uint8_t *ctx_; + const Abi *abi_; + + friend class Host; + }; + + typedef std::map<uint32_t, Host::Thread*> ThreadMap_t; + typedef std::map<std::string, std::string> PropertyMap_t; + typedef std::vector<uint32_t> ThreadVector_t; + + explicit Host(Session *session); + ~Host(); + bool Init(); + + // The following functions are provided cached values when possible. + // For instance, GetSignal, GetThreads, and GetThread, will return + // values that were computed during Update. + // cause a communication between the host and target, so the public + // functions above should be used when possible. + + // Issue a break request if the target is still running. This is + // asynchronous, we won't actually be "broken" until we get the signal + bool Break(); + + // Requests that we cleanly detach from the target. + bool Detach(); + + // Get the current status of the Target. + Status GetStatus(); + + // Get the last signal (which put us into the broken state) + int32_t GetSignal(); + + // Get a list of currently active threads + bool GetThreads(ThreadVector_t *threads) const; + + // Get a thread object by ID. + Thread *GetThread(uint32_t id); + + // Get and set a block of target memory. + bool GetMemory(void *dst, uint64_t addr, uint32_t size); + bool SetMemory(const void *src, uint64_t addr, uint32_t size); + + // Read locally cached properties + bool HasProperty(const char *name) const; + bool ReadProperty(const char *name, std::string *val) const; + + // Read remote object + bool ReadObject(const char *type, const char *name, std::string *val); + + // Set the SINGLE STEP flag on the current thread context, and + // putting the target back into the RUN state. + bool Step(); + + // Issue a step request, putting us back into the RUN state. + bool Continue(); + + // Wait upto the session's packet timeout to see if we receive a break + bool WaitForBreak(); + + // The following functions are internal only and cause communication to + // happen between the target and host. These functions will always + // cause a communication between the host and target, so the public + // functions above should be used when possible. + protected: + // Called whenever the target transitions from running to stopped to + // fetch information about the current state. + bool Update(); + + bool Send(Packet *req, Packet *resp); + bool SendOnly(Packet *req); + + bool Request(const std::string &req, std::string *resp); + bool RequestOnly(const std::string &req); + bool RequestThreadList(ThreadVector_t *ids); + + // Parse a string, returning true and update if a valid stop packet + bool ParseStopPacket(const char *data); + + private: + Session *session_; + const Abi *abi_; + + PropertyMap_t properties_; + ThreadMap_t threads_; + int32_t lastSignal_; + Status status_; +}; + + +} // namespace gdb_rsp + +#endif // NATIVE_CLIENT_GDB_RSP_HOST_H_ + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/host_test.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/host_test.cc new file mode 100644 index 0000000..31576f5 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/host_test.cc @@ -0,0 +1,132 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <string> +#include <sstream> +#include <vector> + +#include "native_client/src/debug_server/gdb_rsp/abi.h" +#include "native_client/src/debug_server/gdb_rsp/host.h" +#include "native_client/src/debug_server/gdb_rsp/session_mock.h" +#include "native_client/src/debug_server/gdb_rsp/test.h" + +using gdb_rsp::Abi; +using gdb_rsp::Host; +using gdb_rsp::SessionMock; + +int VerifyProp(Host* host, const char *key, const char *val) { + std::string out; + if (!host->ReadProperty(key, &out)) { + printf("Could not find key: %s.\n", key); + return 1; + } + + if (out != val) { + printf("Property mismatch\n\tVALUE:%s\n\tEXPECTED:%s\n", + out.data(), val); + return 1; + } + + return 0; +} + +int TestHost() { + int errs = 0; + SessionMock *ses = gdb_rsp::GetGoldenSessionMock(false, false); + const gdb_rsp::Abi* abi = gdb_rsp::Abi::Get(); + + Host *host = new Host(ses); + std::string val; + if (!host->Init()) { + printf("Failed to init host.\n"); + return 1; + } + + // Verify that the Init/Update transactions were + // correctly applied. + if (!host->HasProperty("qXfer:features:read")) { + printf("Missing property: qXfer:features:read\n"); + errs++; + } + + errs += VerifyProp(host, "PacketSize", "7cf"); + errs += VerifyProp(host, "qXfer:libraries:read", "true"); + Host::Thread* thread = host->GetThread(0x1234); + if (NULL == thread) { + printf("Failed to find expected thead 1234.\n"); + errs++; + } else { + for (uint32_t a = 0; a < abi->GetRegisterCount(); a++) { + const Abi::RegDef* def = abi->GetRegisterDef(a); + uint64_t val; + thread->GetRegister(a, &val); + if (static_cast<uint32_t>(val) != a) { + errs++; + printf("Register %s(%d) failed to match expected value: %x", + def->name_, a, static_cast<int>(val)); + break; + } + } + } + + if (host->GetStatus() != Host::HS_STOPPED) { + printf("We expect to be stopped at this point.\n"); + errs++; + } + + if (host->GetSignal() != 5) { + printf("We expect to see signal 5.\n"); + errs++; + } + + if (ses->PeekAction() != SessionMock::DISCONNECT) { + printf("We did not consume all the actions.\n"); + errs++; + } + + // Pop off the DC, and continue processing. + ses->GetAction(); + + // Verify we are now in a running state + ses->AddAction(SessionMock::SEND, "c"); + host->Continue(); + + // We should be running, and timing out on wait + if (host->GetStatus() != Host::HS_RUNNING) { + printf("We expect to be running at this point.\n"); + errs++; + } + if (host->WaitForBreak()) { + printf("We should have timed out.\n"); + errs++; + } + + // Insert a signal 11 for us to wait on + ses->AddAction(SessionMock::RECV, "S11"); + AddMockUpdate(ses, 0x11); + if (!host->WaitForBreak()) { + printf("We should have gotten a signal.\n"); + errs++; + } + if (host->GetStatus() != Host::HS_STOPPED) { + printf("We expect to be stopped at this point.\n"); + errs++; + } + if (host->GetSignal() != 0x11) { + printf("We expect to see signal 11.\n"); + errs++; + } + + delete host; + delete ses; + return errs; +} + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/packet.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/packet.cc new file mode 100644 index 0000000..819abcf --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/packet.cc @@ -0,0 +1,392 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +#include <string> + +#include "native_client/src/debug_server/gdb_rsp/packet.h" +#include "native_client/src/debug_server/gdb_rsp/util.h" +#include "native_client/src/debug_server/port/platform.h" + +using std::string; +using port::IPlatform; + + +namespace gdb_rsp { + +#define MIN_PAD 1 +#define GROW_SIZE 64 + +Packet::Packet() { + seq_ = -1; + Clear(); +} + +void Packet::Clear() { + data_.clear(); + data_.resize(GROW_SIZE); + data_[0] = 0; + + read_index_ = 0; + write_index_ = 0; +} + +void Packet::Rewind() { + read_index_ = 0; +} + +bool Packet::EndOfPacket() const { + return (read_index_ >= write_index_); +} + +void Packet::AddRawChar(char ch) { + // Grow by a fixed amount whenever we are within the pad boundry. + // The pad boundry allows for the addition of NUL termination. + if (data_.size() <= (write_index_ + MIN_PAD)) { + data_.resize(data_.size() + GROW_SIZE); + } + + // Add character and always null terminate. + data_[write_index_++] = ch; + data_[write_index_] = 0; +} + +void Packet::AddWord8(uint8_t ch) { + char seq1, seq2; + + IntToNibble(ch >> 4, &seq1); + IntToNibble(ch & 0xF, &seq2); + + AddRawChar(seq1); + AddRawChar(seq2); +} + +void Packet::AddBlock(const void *ptr, uint32_t len) { + assert(ptr); + + const char *p = (const char *) ptr; + + for (uint32_t offs = 0; offs < len; offs++) { + AddWord8(p[offs]); + } +} + +void Packet::AddWord16(uint16_t val) { + AddBlock(&val, sizeof(val)); +} + +void Packet::AddWord32(uint32_t val) { + AddBlock(&val, sizeof(val)); +} + +void Packet::AddWord64(uint64_t val) { + AddBlock(&val, sizeof(val)); +} + +void Packet::AddString(const char *str) { + assert(str); + + while (*str) { + AddRawChar(*str); + str++; + } +} + +void Packet::AddHexString(const char *str) { + assert(str); + + while (*str) { + AddWord8(*str); + str++; + } +} + +void Packet::AddNumberSep(uint64_t val, char sep) { + char out[sizeof(val) * 2]; + int nibbles = 0; + size_t a; + + // Check for -1 optimization + if (val == static_cast<uint64_t>(-1)) { + AddRawChar('-'); + AddRawChar('1'); + } else { + // Assume we have the valuse 0x00001234 + for (a = 0; a < sizeof(val); a++) { + uint8_t byte = static_cast<uint8_t>(val & 0xFF); + + // Stream in with bytes reverse, starting at least significant + // So we store 4, then 3, 2, 1 + IntToNibble(byte & 0xF, &out[nibbles++]); + IntToNibble(byte >> 4, &out[nibbles++]); + + // Get the next 8 bits; + val >>= 8; + + // Supress leading zeros, so we are done when val hits zero + if (val == 0) break; + } + + // Strip the high zero for this byte if needed + if ((nibbles > 1) && (out[nibbles-1] == '0')) nibbles--; + + // Now write it out reverse to correct the order + while (nibbles) { + nibbles--; + AddRawChar(out[nibbles]); + } + } + + // If we asked for a sperator, insert it + if (sep) AddRawChar(sep); +} + +bool Packet::GetNumberSep(uint64_t *val, char *sep) { + uint64_t out = 0; + char ch; + + if (!GetRawChar(&ch)) return false; + + // Check for -1 + if (ch == '-') { + if (!GetRawChar(&ch)) return false; + + if (ch == '1') { + *val = -1; + + ch = 0; + GetRawChar(&ch); + if (sep) *sep = ch; + return true; + } + return false; + } + + do { + int nib; + + // Check for separator + if (!NibbleToInt(ch, &nib)) break; + + // Add this nibble. + out = (out << 4) + nib; + + // Get the next character (if availible) + ch = 0; + if (!GetRawChar(&ch)) break; + } while (1); + + // Set the value; + *val = out; + + // Add the separator if the user wants it... + if (sep != NULL) *sep = ch; + + return true; +} + +bool Packet::GetRawChar(char *ch) { + assert(ch != NULL); + + if (read_index_ >= write_index_) + return false; + + *ch = data_[read_index_++]; + + // Check for RLE X*N, where X is the value, N is the reps. + if (*ch == '*') { + if (read_index_ < 2) { + IPlatform::LogError("Unexpected RLE at start of packet.\n"); + return false; + } + + if (read_index_ >= write_index_) { + IPlatform::LogError("Unexpected EoP during RLE.\n"); + return false; + } + + // GDB does not use "CTRL" characters in the stream, so the + // number of reps is encoded as the ASCII value beyond 28 + // (which when you add a min rep size of 4, forces the rep + // character to be ' ' (32) or greater). + int32_t cnt = (data_[read_index_] - 28); + if (cnt < 3) { + IPlatform::LogError("Unexpected RLE length.\n"); + return false; + } + + // We have just read '*' and incremented the read pointer, + // so here is the old state, and expected new state. + // + // Assume N = 5, we grow by N - size of encoding (3). + // + // OldP: R W + // OldD: 012X*N89 = 8 chars + // Size: 012X*N89__ = 10 chars + // Move: 012X*__N89 = 10 chars + // Fill: 012XXXXX89 = 10 chars + // NewP: R W (shifted 5 - 3) + // + // To accomplish this we must first, resize the vector then move + // all remaining characters to the right, by the delta between + // the run length, and encoding size. This moves one more char + // than needed (the 'N'), but is easier to understand. + // NOTE: We add one to the resize to allow for zero termination. + data_.resize(write_index_ + cnt - 3 + 1); + memmove(&data_[read_index_ + cnt - 3], &data_[read_index_], + write_index_ - read_index_); + + // Now me must go back and fill over the previous '*' with the + // repeated character for the length of the run minus the original + // character which is already correct + *ch = data_[read_index_ - 2]; + memset(&data_[read_index_ - 1], *ch, cnt - 1); + + // Now we update the write_index_, and reterminate the string. + write_index_ = data_.size() - 1; + data_[write_index_] = 0; + } + return true; +} + +bool Packet::GetWord8(uint8_t *ch) { + assert(ch); + + char seq1, seq2; + int val1, val2; + + // Get two ASCII hex values + if (!GetRawChar(&seq1)) return false; + if (!GetRawChar(&seq2)) return false; + + // Convert them to ints + if (!NibbleToInt(seq1, &val1)) return false; + if (!NibbleToInt(seq2, &val2)) return false; + + *ch = (val1 << 4) + val2; + return true; +} + +bool Packet::GetBlock(void *ptr, uint32_t len) { + assert(ptr); + + uint8_t *p = reinterpret_cast<uint8_t *>(ptr); + bool res = true; + + for (uint32_t offs = 0; offs < len; offs++) { + res = GetWord8(&p[offs]); + if (false == res) break; + } + + return res; +} + +bool Packet::GetWord16(uint16_t *ptr) { + assert(ptr); + return GetBlock(ptr, sizeof(*ptr)); +} + +bool Packet::GetWord32(uint32_t *ptr) { + assert(ptr); + return GetBlock(ptr, sizeof(*ptr)); +} + +bool Packet::GetWord64(uint64_t *ptr) { + assert(ptr); + return GetBlock(ptr, sizeof(*ptr)); +} + + +bool Packet::GetString(string* str) { + if (EndOfPacket()) return false; + + *str = &data_[read_index_]; + read_index_ = write_index_; + return true; +} + +bool Packet::GetHexString(string* str) { + char ch; + if (EndOfPacket()) return false; + + // Pull values until we hit a seperator + str->clear(); + while (GetRawChar(&ch)) { + if (NibbleToInt(ch, NULL)) { + *str += ch; + } else { + read_index_--; + break; + } + } + return true; +} + +bool Packet::GetStringCB(void *ctx, StrFunc_t cb) { + assert(NULL != ctx); + + if (EndOfPacket()) { + cb(ctx, NULL); + return false; + } + + cb(ctx, &data_[read_index_]); + read_index_ = write_index_; + return true; +} + +bool Packet::GetHexStringCB(void *ctx, StrFunc_t cb) { + assert(NULL != ctx); + + std::string out; + char ch; + + if (EndOfPacket()) { + cb(ctx, NULL); + return false; + } + + // Pull values until we hit a seperator + while (GetRawChar(&ch)) { + if (NibbleToInt(ch, NULL)) { + out += ch; + } else { + read_index_--; + break; + } + } + + // Call the CB with the availible string + cb(ctx, out.data()); + return true; +} + + +const char *Packet::GetPayload() const { + return &data_[0]; +} + +bool Packet::GetSequence(int32_t *ch) const { + assert(ch); + + if (seq_ != -1) { + *ch = seq_; + return true; + } + + return false; +} + +void Packet::SetSequence(int32_t val) { + seq_ = val; +} + +} // namespace gdb_rsp + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/packet.h b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/packet.h new file mode 100644 index 0000000..d94c24e --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/packet.h @@ -0,0 +1,122 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + + +// This module provides interfaces for writing to and reading from a +// GDB RSP packet. A packet represents a single data transfer from one +// RSP endpoint to another excluding all additional encapsulating data +// generated by the session layer. See: +// http://ftp.gnu.org/old-gnu/Manuals/gdb-5.1.1/html_node/gdb_129.html#SEC134 +// +// The packet object stores the data in a character vector in a cooked format, +// tracking both a read and write position within the stream. An empty +// packet still contains a valid pointer to NULL, so it is always safe to +// get the Payload and use it as a string. In addition packets may be +// sequenced by setting an 8 bit sequence number which helps both sides +// detect when packets have been lost. By default the sequence number is not +// set which is represented as a -1. +// +// This API is not expected to throw unless the underlying vector attempts +// to resize when the system is out of memory, in which case it will throw +// a std::bad_alloc. +#ifndef NATIVE_CLIENT_GDB_RSP_PACKET_H_ +#define NATIVE_CLIENT_GDB_RSP_PACKET_H_ 1 + +#include <string> +#include <vector> + +#include "native_client/src/debug_server/port/std_types.h" + +namespace gdb_rsp { + +class Packet { + typedef void (*StrFunc_t)(void *ctx, const char *str); + + public: + Packet(); + + // Empty the vector and reset the read/write pointers. + void Clear(); + + // Reset the read pointer, allowing the packet to be re-read. + void Rewind(); + + // Return true of the read pointer has reached the write pointer. + bool EndOfPacket() const; + + // Store a single raw 8 bit value + void AddRawChar(char ch); + + // Store a block of data as hex pairs per byte + void AddBlock(const void *ptr, uint32_t len); + + // Store an 8, 16, 32, or 64 bit word as a block without removing preceeding + // zeros. This is used for fixed sized fields. + void AddWord8(uint8_t val); + void AddWord16(uint16_t val); + void AddWord32(uint32_t val); + void AddWord64(uint64_t val); + + // Store a number up to 64 bits, with preceeding zeros removed. Since + // zeros can be removed, the width of this number is unknown, and always + // followed by NUL or a seperator (non hex digit). + void AddNumberSep(uint64_t val, char sep); + + // Add a raw string. This is dangerous since the other side may incorrectly + // interpret certain special characters such as: ":,#$" + void AddString(const char *str); + + // Add a string stored as a stream of ASCII hex digit pairs. It is safe + // to use any character in this stream. If this does not terminate the + // packet, there should be a sperator (non hex digit) immediately following. + void AddHexString(const char *str); + + // Retrieve a single character if available + bool GetRawChar(char *ch); + + // Retreive "len" ASCII character pairs. + bool GetBlock(void *ptr, uint32_t len); + + // Retreive a 8, 16, 32, or 64 bit word as pairs of hex digits. These + // functions will always consume bits/4 characters from the stream. + bool GetWord8(uint8_t *val); + bool GetWord16(uint16_t *val); + bool GetWord32(uint32_t *val); + bool GetWord64(uint64_t *val); + + // Retreive a number and the seperator. If SEP is null, the seperator is + // consumed but thrown away. + bool GetNumberSep(uint64_t *val, char *sep); + + // Get a string from the stream + bool GetString(std::string *str); + bool GetHexString(std::string *str); + + // Callback with the passed in context, and a NUL terminated string. + // These methods provide a means to avoid an extra memcpy. + bool GetStringCB(void *ctx, StrFunc_t cb); + bool GetHexStringCB(void *ctx, StrFunc_t cb); + + // Return a pointer to the entire packet payload + const char *GetPayload() const; + + // Returns true and the sequence number, or false if it is unset. + bool GetSequence(int32_t *seq) const; + + // Set the sequence number. + void SetSequence(int32_t seq); + + private: + int32_t seq_; + std::vector<char> data_; + size_t read_index_; + size_t write_index_; +}; + +} // namespace gdb_rsp + +#endif // NATIVE_CLIENT_GDB_RSP_PACKET_H_ + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/packet_test.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/packet_test.cc new file mode 100644 index 0000000..dc94d81 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/packet_test.cc @@ -0,0 +1,106 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <string> +#include <sstream> +#include <vector> + +#include "native_client/src/debug_server/gdb_rsp/packet.h" +#include "native_client/src/debug_server/gdb_rsp/test.h" + +using gdb_rsp::Packet; + +int VerifyPacket(Packet *wr, Packet *rd, void *ctx, PacketFunc_t tx) { + int errs = 0; + char ch; + std::string str; + + uint8_t byte = 0; + uint16_t word16 = 0; + uint32_t word32 = 0; + + // Clear the packet may be reused + wr->Clear(); + + // Verify non alligned writes + wr->AddRawChar('X'); + wr->AddWord16(0x1234); + wr->AddWord32(0x56789ABC); + wr->AddWord8(0xFF); + if (tx) tx(ctx, wr, rd); + rd->GetString(&str); + if (strcmp("X3412bc9a7856ff", str.data())) errs++; + + // Verify rewind + rd->Rewind(); + rd->GetRawChar(&ch); + if (ch != 'X') errs++; + + rd->GetWord16(&word16); + if (word16 != 0x1234) errs++; + + rd->GetWord32(&word32); + if (word32 != 0x56789ABC) errs++; + + rd->GetWord8(&byte); + if (byte != 0xFF) errs++; + + // Check Empty Send + wr->Clear(); + if (tx) tx(ctx, wr, rd); + if (rd->GetRawChar(&ch)) { + errs++; + printf("Was expecting an empty packet\n"); + } + + // Check nibble/byte order + // Check edge cases of 1, 0, -1, -2 + wr->AddNumberSep(0x123, ','); + wr->AddNumberSep(1, ','); + wr->AddNumberSep(0, ','); + wr->AddNumberSep(static_cast<uint64_t>(-1), ','); + wr->AddNumberSep(static_cast<uint64_t>(-2), 0); + if (tx) tx(ctx, wr, rd); + rd->GetString(&str); + if (strcmp("123,1,0,-1,fffffffffffffffe", str.data()) != 0) errs++; + + // We push "compress" which was generated by a real gdbserver, through into + // our packet, then pull it out as a block to decompress, then check against + // the 32b values generated by a real gdb debugger. (Inline golden file) + const char *compress = "0*488c7280038c72800d4c72800030**fd00637702020* 23" + "0*\"2b0*\"2b0*\"2b0*\"530*\"2b0*}00080f83f00c022f8" + "07c022f80f3c80040*&80ff3f0**80ff3f7f030* 30*\"0f*" + " 0* 58050* 85f7196c2b0*\"b0b801010*}0*}0*b801f0* "; + uint32_t vals[16] = { 0, 0, 0, 0x28C788, 0x28C738, 0x28c7d4, 3, 0, 0x776300FD, + 0x202, 0x23, 0x2B, 0x2B, 0x2B, 0x53, 0x2B }; + uint32_t tmp[16]; + + wr->Clear(); + wr->AddString(compress); + if (tx) tx(ctx, wr, rd); + rd->GetBlock(tmp, sizeof(vals)); + if (memcmp(tmp, vals, sizeof(vals))) { + errs++; + printf("Failed to decompress as expected.\n"); + } + + if (errs) + printf("FAILED PACKET TEST\n"); + + return errs; +} + +int TestPacket() { + int errs = 0; + Packet pkt; + + errs += VerifyPacket(&pkt, &pkt, NULL, NULL); + return errs; +} diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session.cc new file mode 100644 index 0000000..f62ad87 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session.cc @@ -0,0 +1,316 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +#include <string> +#include <sstream> + +#include "native_client/src/debug_server/gdb_rsp/packet.h" +#include "native_client/src/debug_server/gdb_rsp/session.h" +#include "native_client/src/debug_server/gdb_rsp/util.h" + +#include "native_client/src/debug_server/port/mutex.h" +#include "native_client/src/debug_server/port/platform.h" +#include "native_client/src/debug_server/port/transport.h" + +using port::IPlatform; +using port::ITransport; +using port::IMutex; +using port::MutexLock; + +// Use a timeout of 1 second +int const kSessionTimeoutMs = 1000; + +namespace gdb_rsp { + +Session::Session() + : mutex_(NULL), + io_(NULL), + flags_(0), + seq_(0), + connected_(false) { +} + +Session::~Session() { + if (mutex_) IMutex::Free(mutex_); +} + + +bool Session::Init(port::ITransport *transport) { + if (NULL == transport) return false; + + mutex_ = IMutex::Allocate(); + if (NULL == mutex_) return false; + + connected_ = true; + io_ = transport; + return true; +} + +void Session::SetFlags(uint32_t flags) { + flags_ |= flags; +} + +void Session::ClearFlags(uint32_t flags) { + flags_ &= ~flags; +} + +uint32_t Session::GetFlags() { + return flags_; +} + +bool Session::DataAvailable() { + assert(io_); + + return io_->ReadWaitWithTimeout(kSessionTimeoutMs); +} + +bool Session::Connected() { + return connected_; +} + +bool Session::GetChar(char *ch) { + assert(io_); + + // Attempt to select this IO for reading. + if (DataAvailable() == false) return false; + + int32_t len = io_->Read(ch, 1); + + // If data is "availible" but we can't read, it must be closed. + if (len < 1) { + io_->Disconnect(); + connected_ = false; + return false; + } + + return true; +} + + +bool Session::SendPacket(Packet *pkt) { + MutexLock lock(mutex_); + char ch; + + do { + if (!SendPacketOnly(pkt)) return false; + + // If ACKs are off, we are done. + if (GetFlags() & IGNORE_ACK) break; + + // Otherwise, poll for '+' + if (!GetChar(&ch)) return false; + + // Retry if we didn't get a '+' + } while (ch != '+'); + + return true; +} + + +bool Session::SendPacketOnly(Packet *pkt) { + MutexLock lock(mutex_); + + const char *ptr; + char ch; + std::stringstream outstr; + + char run_xsum = 0; + int32_t seq; + + ptr = pkt->GetPayload(); + + if (!pkt->GetSequence(&seq) && (GetFlags() & USE_SEQ)) { + pkt->SetSequence(seq_++); + } + + // Signal start of response + outstr << '$'; + + // If there is a sequence, send as two nibble 8bit value + ':' + if (pkt->GetSequence(&seq)) { + IntToNibble((seq & 0xFF) >> 4, &ch); + outstr << ch; + run_xsum += ch; + + IntToNibble(seq & 0xF, &ch); + outstr << ch; + run_xsum += ch; + + ch = ':'; + outstr << ch; + run_xsum += ch; + } + + // Send the main payload + int offs = 0; + while ((ch = ptr[offs++]) != 0) { + outstr << ch; + run_xsum += ch; + } + + // Send XSUM as two nible 8bit value preceeded by '#' + outstr << '#'; + IntToNibble((run_xsum >> 4) & 0xF, &ch); + outstr << ch; + IntToNibble(run_xsum & 0xF, &ch); + outstr << ch; + + return SendStream(outstr.str().data()); +} + +// We do not take the mutex here since we already have it +// this function is protected so it can't be called directly. +bool Session::SendStream(const char *out) { + int32_t len = static_cast<int32_t>(strlen(out)); + int32_t sent = 0; + + assert(io_); + + while (sent < len) { + const char *cur = &out[sent]; + int32_t tx = io_->Write(cur, len - sent); + + if (tx <= 0) { + IPlatform::LogWarning("Send of %d bytes : '%s' failed.\n", len, out); + io_->Disconnect(); + connected_ = false; + return false; + } + + sent += tx; + } + + if (GetFlags() & DEBUG_SEND) IPlatform::LogInfo("TX %s\n", out); + return true; +} + + +// Attempt to receive a packet +bool Session::GetPacket(Packet *pkt) { + assert(io_); + + MutexLock lock(mutex_); + + char run_xsum, fin_xsum, ch; + std::string in; + int has_seq, offs; + + // If nothing is waiting, return false + if (!io_->ReadWaitWithTimeout(kSessionTimeoutMs)) return false; + + // Toss characters until we see a start of command + do { + if (!GetChar(&ch)) return false; + in += ch; + } while (ch != '$'); + + retry: + has_seq = 1; + offs = 0; + + // If nothing is waiting, return NONE + if (!io_->ReadWaitWithTimeout(kSessionTimeoutMs)) return false; + + // Clear the stream + pkt->Clear(); + + // Prepare XSUM calc + run_xsum = 0; + fin_xsum = 0; + + // Stream in the characters + while (1) { + if (!GetChar(&ch)) return false; + + in += ch; + // Check SEQ statemachine xx: + switch (offs) { + case 0: + case 1: + if (!NibbleToInt(ch, 0)) has_seq = 0; + break; + + case 2: + if (ch != ':') has_seq = 0; + break; + } + offs++; + + // If we see a '#' we must be done with the data + if (ch == '#') break; + + // If we see a '$' we must have missed the last cmd + if (ch == '$') { + IPlatform::LogInfo("RX Missing $, retry.\n"); + goto retry; + } + // Keep a running XSUM + run_xsum += ch; + pkt->AddRawChar(ch); + } + + + // Get two Nibble XSUM + if (!GetChar(&ch)) return false; + in += ch; + + int val; + NibbleToInt(ch, & val); + fin_xsum = val << 4; + + if (!GetChar(&ch)) return false; + in += ch; + NibbleToInt(ch, &val); + fin_xsum |= val; + + if (GetFlags() & DEBUG_RECV) IPlatform::LogInfo("RX %s\n", in.data()); + + // Pull off teh sequence number if we have one + if (has_seq) { + uint8_t seq; + char ch; + + pkt->GetWord8(&seq); + pkt->SetSequence(seq); + pkt->GetRawChar(&ch); + if (ch != ':') { + IPlatform::LogError("RX mismatched SEQ.\n"); + return false; + } + } + + // If ACKs are off, we are done. + if (GetFlags() & IGNORE_ACK) return true; + + // If the XSUMs don't match, signal bad packet + if (fin_xsum == run_xsum) { + char out[4] = { '+', 0, 0, 0}; + int32_t seq; + + // If we have a sequence number + if (pkt->GetSequence(&seq)) { + // Respond with Sequence number + IntToNibble(seq >> 4, &out[1]); + IntToNibble(seq & 0xF, &out[2]); + } + return SendStream(out); + } else { + // Resend a bad XSUM and look for retransmit + SendStream("-"); + + IPlatform::LogInfo("RX Bad XSUM, retry\n"); + goto retry; + } + + return true; +} + +} // End of namespace gdb_rsp + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session.h b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session.h new file mode 100644 index 0000000..ee3ce31 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session.h @@ -0,0 +1,86 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + + +// This module provides interfaces for writing to and reading from a +// GDB RSP connection. The connection uses a generic transport +// object for sending packets from one endpoint to another. The session +// is responsible to delievery including generation of checksums and +// retransmits and handling timeouts as needed. See: +// http:// ftp.gnu.org/old-gnu/Manuals/gdb-5.1.1/html_node/gdb_129.html#SEC134 +// +// All data is read one character at a time through 'GetChar' which will +// poll on DataAvail and timeout after one second, allowing the session +// to handle blocking transports. +// +// The session object is not reentrant, and will have unpredictable +// results if two threads attempt to read from the session at the same +// time. +// +// This module may throw a std::bad_alloc if there is an error while trying +// to resize the packet. In addition, the underlying ITransport is free +// to throw an exception if the connection is lost, which will pass out +// of any packet send or receive function. +#ifndef NATIVE_CLIENT_GDB_RSP_SESSION_H_ +#define NATIVE_CLIENT_GDB_RSP_SESSION_H_ 1 + +#include <sstream> + +#include "native_client/src/debug_server/port/std_types.h" +#include "native_client/src/debug_server/port/mutex.h" +#include "native_client/src/debug_server/port/transport.h" + +namespace gdb_rsp { + +class Packet; + +// Session is not inteded to be derived from, protected members are +// protected only for unit testing purposes. +class Session { + public: + Session(); + virtual ~Session(); + + enum { + IGNORE_ACK = 1, // Do not emit or wait for '+' from RSP stream. + USE_SEQ = 2, // Automatically use a sequence number + DEBUG_SEND = 4, // Log all SENDs + DEBUG_RECV = 8, // Log all RECVs + DEBUG_MASK = (DEBUG_SEND | DEBUG_RECV) + }; + + public: + virtual bool Init(port::ITransport *transport); + virtual void SetFlags(uint32_t flags); + virtual void ClearFlags(uint32_t flags); + virtual uint32_t GetFlags(); + + virtual bool SendPacketOnly(Packet *packet); + virtual bool SendPacket(Packet *packet); + virtual bool GetPacket(Packet *packet); + virtual bool DataAvailable(); + virtual bool Connected(); + + protected: + virtual bool GetChar(char *ch); + virtual bool SendStream(const char *str); + + private: + Session(const Session&); + Session &operator=(const Session&); + + protected: + port::IMutex *mutex_; // Lock to enforce correct response order. + port::ITransport *io_; // Transport object not owned by the Session. + uint32_t flags_; // Session flags for Sequence/Ack generation. + uint8_t seq_; // Next sequence number to use or -1. + bool connected_; // Is the connection still valid. +}; + +} // namespace gdb_rsp + +#endif // NATIVE_CLIENT_GDB_RSP_SESSION_H_ + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session_mock.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session_mock.cc new file mode 100644 index 0000000..55afe22 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session_mock.cc @@ -0,0 +1,192 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <assert.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include <string> +#include <sstream> + +#include "native_client/src/debug_server/gdb_rsp/abi.h" +#include "native_client/src/debug_server/gdb_rsp/session.h" +#include "native_client/src/debug_server/gdb_rsp/session_mock.h" +#include "native_client/src/debug_server/port/platform.h" +#include "native_client/src/debug_server/port/thread.h" +#include "native_client/src/debug_server/port/transport.h" + +#ifdef WIN32 +#define snprintf _snprintf +#endif + +namespace gdb_rsp { + +SessionMock::SessionMock(bool target) : Session() { + target_ = target; + cur_ = 0; + + Session::Init(new TransportMock); + SetFlags(IGNORE_ACK); +} + +SessionMock::~SessionMock() { + for (size_t a = 0; a < actions_.size(); a++) + delete actions_[a]; + + delete static_cast<TransportMock*>(io_); +} + +bool SessionMock::SendPacketOnly(gdb_rsp::Packet *packet) { + if (PeekAction() == SEND) { + Action *pact = GetAction(); + const char *str = packet->GetPayload(); + + if (GetFlags() & DEBUG_SEND) port::IPlatform::LogInfo("TX %s\n", str); + + // Check if we match + if (pact->str_ == str) return true; + + port::IPlatform::LogInfo("Mismatch:\n\tSENDING: %s\n\tEXPECT: %s\n", + str, pact->str_.data()); + } + return false; +} + +bool SessionMock::GetPacket(gdb_rsp::Packet *packet) { + if (PeekAction() == RECV) { + Action *pact = GetAction(); + packet->Clear(); + packet->AddString(pact->str_.data()); + if (GetFlags() & DEBUG_RECV) { + port::IPlatform::LogInfo("RX %s\n", pact->str_.data()); + } + return true; + } + return false; +} + +bool SessionMock::DataAvailable() { + return PeekAction() == RECV; +} + +SessionMock::ActionType SessionMock::PeekAction() { + if (cur_ < actions_.size()) { + Action* pact = actions_[cur_]; + ActionType at = pact->type_; + if (DISCONNECT == at) { + connected_ = false; + } + return at; + } + return TIMEOUT; +} + +SessionMock::Action* SessionMock::GetAction() { + if (cur_ < actions_.size()) return actions_[cur_++]; + return NULL; +} + +void SessionMock::AddAction(ActionType act, const char *str) { + Action *pact = new Action; + + pact->type_ = act; + pact->str_ = str; + actions_.push_back(pact); +} + +void SessionMock::AddTransaction(const char *snd, const char *rcv) { + if (target_) { + // If we are a target, we get a command, then respond + AddAction(RECV, snd); + AddAction(SEND, rcv); + } else { + // If we are a host, we send a command, and expect a reply + AddAction(SEND, snd); + AddAction(RECV, rcv); + } +} + +void AddMockInit(SessionMock* ses) { + const gdb_rsp::Abi* abi = gdb_rsp::Abi::Get(); + + // Setup supported query + ses->AddTransaction( + "qSupported", + "PacketSize=7cf;qXfer:libraries:read+;qXfer:features:read+"); + + // Setup arch query + std::string str = "l<target><architecture>"; + str += abi->GetName(); + str += "</architecture></target>"; + ses->AddTransaction( + "qXfer:features:read:target.xml:0,7cf", + str.data()); +} + +void AddMockUpdate(SessionMock* ses, uint8_t sig) { + const gdb_rsp::Abi* abi = gdb_rsp::Abi::Get(); + + // Setup thread query + ses->AddTransaction( + "qfThreadInfo", + "m1234"); + ses->AddTransaction( + "qsThreadInfo", + "l"); + ses->AddTransaction( + "Hg1234", + "OK"); + + // Setup Register Query + std::string str = ""; + port::IThread *thread = port::IThread::Acquire(0x1234, true); + for (uint32_t a = 0; a < abi->GetRegisterCount(); a++) { + char tmp[8]; + const Abi::RegDef* def = abi->GetRegisterDef(a); + uint64_t val = static_cast<uint64_t>(a); + uint8_t *pval = reinterpret_cast<uint8_t*>(&val); + + // Create a hex version of the register number of + // the specified zero padded to the correct size + for (uint32_t b = 0; b < def->bytes_; b++) { + snprintf(tmp, sizeof(tmp), "%02x", pval[b]); + str += tmp; + } + + thread->SetRegister(a, &val, def->bytes_); + } + ses->AddTransaction("g", str.data()); + + char tmp[8]; + snprintf(tmp, sizeof(tmp), "S%02x", sig); + ses->AddTransaction("?", tmp); +} + +// GetGoldenSessionMock +// +// The function bellow generates a mock session and preloads it +// with expected inputs and outputs for a common connection case. +// For GDB, that's: +// 1) qSupported - Querry for supported features +// 2) qXfer:features:read:target.xml:0,7cf - Query for target arch. +// 3) qfThreadInfo/qsThreadInfo - Query for active threads +// 4) Hg1234 - Set current register context to retreived thread id +// 5) g - Get current thread registers +// 6) disconnect +SessionMock *GetGoldenSessionMock(bool target, bool debug) { + SessionMock *ses = new SessionMock(target); + + if (debug) ses->SetFlags(Session::DEBUG_RECV | Session::DEBUG_SEND); + + AddMockInit(ses); + AddMockUpdate(ses, 5); + + ses->AddAction(SessionMock::DISCONNECT, ""); + return ses; +} + +} // namespace gdb_rsp diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session_mock.h b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session_mock.h new file mode 100644 index 0000000..f6c7b28 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session_mock.h @@ -0,0 +1,78 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ +#ifndef NATIVE_CLIENT_GDB_RSP_SESSION_MOCK_H_ +#define NATIVE_CLIENT_GDB_RSP_SESSION_MOCK_H_ 1 + +#include <string> +#include <vector> + +#include "native_client/src/debug_server/gdb_rsp/packet.h" +#include "native_client/src/debug_server/gdb_rsp/session.h" +#include "native_client/src/debug_server/port/platform.h" +#include "native_client/src/debug_server/port/transport.h" + +namespace gdb_rsp { + +class SessionMock : public Session { + public: + enum ActionType { + SEND, + RECV, + TIMEOUT, + DISCONNECT + }; + + struct Action { + std::string str_; + ActionType type_; + }; + + class TransportMock : public port::ITransport { + public: + virtual int32_t Read(void *ptr, int32_t len) { + (void) ptr; + (void) len; + return -1; + } + virtual int32_t Write(const void *ptr, int32_t len) { + (void) ptr; + (void) len; + return -1; + } + virtual bool ReadWaitWithTimeout(uint32_t ms) { + (void) ms; + return false; + } + virtual void Disconnect() { } + }; + + explicit SessionMock(bool target); + ~SessionMock(); + + bool SendPacketOnly(gdb_rsp::Packet *packet); + bool GetPacket(gdb_rsp::Packet *packet); + bool DataAvailable(); + ActionType PeekAction(); + Action *GetAction(); + + void AddAction(ActionType act, const char *str); + void AddTransaction(const char *snd, const char *rcv); + + private: + std::vector<Action*> actions_; + size_t cur_; + bool target_; +}; + +SessionMock *GetGoldenSessionMock(bool target, bool debug); +void AddMockInit(SessionMock *ses); +void AddMockUpdate(SessionMock *ses, uint8_t sig); + + + +} // namespace gdb_rsp + +#endif // NATIVE_CLIENT_GDB_RSP_SESSION_MOCK_H_ diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session_test.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session_test.cc new file mode 100644 index 0000000..da35f27 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/session_test.cc @@ -0,0 +1,285 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <string> +#include <sstream> + +#include "native_client/src/debug_server/gdb_rsp/session.h" +#include "native_client/src/debug_server/gdb_rsp/test.h" +#include "native_client/src/debug_server/port/platform.h" + +using gdb_rsp::Session; +using gdb_rsp::Packet; + +// Transport simulation class, this stores data and a r/w index +// to simulate one direction of a pipe, or a pipe to self. +class SharedVector { + public: + SharedVector() : rd(0), wr(0) {} + + public: + std::vector<char> data; + volatile uint32_t rd; + volatile uint32_t wr; +}; + +// Simulates a transport (such as a socket), the reports "ready" +// when polled, but fails on TX/RX. +class DCSocketTransport : public port::ITransport { + public: + virtual int32_t Read(void *ptr, int32_t len) { + (void) ptr; + (void) len; + return -1; + } + + virtual int32_t Write(const void *ptr, int32_t len) { + (void) ptr; + (void) len; + return -1; + } + + virtual bool ReadWaitWithTimeout(uint32_t ms) { + (void) ms; + return true; + } + + virtual void Disconnect() {} + virtual bool DataAvail() { return true; } +}; + + +// Simulate a transport transmitting data Q'd in TX and verifying that +// inbound data matches expected "golden" string. +class GoldenTransport : public port::ITransport { + public: + GoldenTransport(const char *rx, const char *tx, int cnt) { + rx_ = rx; + tx_ = tx; + cnt_ = cnt; + txCnt_ = 0; + rxCnt_ = 0; + errs_ = 0; + disconnected_ = false; + } + + virtual int32_t Read(void *ptr, int32_t len) { + if (disconnected_) return -1; + memcpy(ptr, &rx_[rxCnt_], len); + rxCnt_ += len; + if (static_cast<int>(strlen(rx_)) < rxCnt_) { + printf("End of RX\n"); + errs_++; + } + return len; + } + + // Read from this link, return a negative value if there is an error + virtual int32_t Write(const void *ptr, int32_t len) { + const char *str = reinterpret_cast<const char *>(ptr); + if (disconnected_) return -1; + if (strncmp(str, &tx_[txCnt_], len) != 0) { + printf("TX mismatch in %s vs %s.\n", str, &tx_[txCnt_]); + errs_++; + } + txCnt_ += len; + return len; + } + + virtual bool ReadWaitWithTimeout(uint32_t ms) { + if (disconnected_) return true; + + for (int loop = 0; loop < 8; loop++) { + if (DataAvail()) return true; + port::IPlatform::Relinquish(ms >> 3); + } + return false; + } + + virtual void Disconnect() { + disconnected_ = true; + } + + virtual bool DataAvail() { + return rxCnt_ < static_cast<int>(strlen(rx_)); + } + + int errs() { return errs_; } + + + protected: + const char *rx_; + const char *tx_; + int cnt_; + int rxCnt_; + int txCnt_; + int errs_; + bool disconnected_; +}; + + +class TestTransport : public port::ITransport { + public: + TestTransport(SharedVector *rvec, SharedVector *wvec) { + rvector_ = rvec; + wvector_ = wvec; + disconnected_ = false; + } + + virtual int32_t Read(void *ptr, int32_t len) { + if (disconnected_) return -1; + DataAvail(); + + int max = rvector_->wr - rvector_->rd; + if (max > len) + max = len; + + if (max > 0) { + char *src = &rvector_->data[rvector_->rd]; + memcpy(ptr, src, max); + } + rvector_->rd += max; + return max; + } + + virtual int32_t Write(const void *ptr, int32_t len) { + if (disconnected_) return -1; + + wvector_->data.resize(wvector_->wr + len); + memcpy(&wvector_->data[wvector_->wr], ptr, len); + wvector_->wr += len; + return len; + } + + virtual bool ReadWaitWithTimeout(uint32_t ms) { + if (disconnected_) return true; + + for (int loop = 0; loop < 8; loop++) { + if (DataAvail()) return true; + port::IPlatform::Relinquish(ms >> 3); + } + return false; + } + + virtual void Disconnect() { + disconnected_ = true; + } + + // Return true if vec->data is availible ( + virtual bool DataAvail() { + return (rvector_->rd < rvector_->wr); + } + + protected: + SharedVector *rvector_; + SharedVector *wvector_; + bool disconnected_; +}; + + +int TestSession() { + int errs = 0; + Packet pktOut; + Packet pktIn; + SharedVector vec; + + // Create a "loopback" session by using the same + // FIFO for ingress and egress. + Session cli; + Session srv; + + if (cli.Init(NULL)) { + printf("Initializing with NULL did not fail.\n"); + errs++; + } + + cli.Init(new TestTransport(&vec, &vec)); + srv.Init(new TestTransport(&vec, &vec)); + + // Check, Set,Clear,Get flags. + cli.ClearFlags(static_cast<uint32_t>(-1)); + cli.SetFlags(Session::IGNORE_ACK | Session::DEBUG_RECV); + if (cli.GetFlags() != (Session::IGNORE_ACK + Session::DEBUG_RECV)) { + printf("SetFlag failed.\n"); + errs++; + } + cli.ClearFlags(Session::IGNORE_ACK | Session::DEBUG_SEND); + if (cli.GetFlags() != Session::DEBUG_RECV) { + printf("ClearFlag failed.\n"); + errs++; + } + + // Check Send Packet of known value. + const char *str = "1234"; + + pktOut.AddString(str); + cli.SendPacketOnly(&pktOut); + srv.GetPacket(&pktIn); + std::string out; + pktIn.GetString(&out); + if (out != str) { + printf("Send Only failed.\n"); + errs++; + } + + // Check send against golden transactions + const char tx[] = { "$1234#ca+" }; + const char rx[] = { "+$OK#9a" }; + GoldenTransport gold(rx, tx, 2); + Session uni; + uni.Init(&gold); + + pktOut.Clear(); + pktOut.AddString(str); + if (!uni.SendPacket(&pktOut)) { + printf("Send failed.\n"); + errs++; + } + if (!uni.GetPacket(&pktIn)) { + printf("Get failed.\n"); + errs++; + } + pktIn.GetString(&out); + if (out != "OK") { + printf("Send/Get failed.\n"); + errs++; + } + + // Check that a closed Transport reports to session + if (!uni.Connected()) { + printf("Expecting uni to be connected.\n"); + errs++; + } + gold.Disconnect(); + uni.GetPacket(&pktIn); + if (uni.Connected()) { + printf("Expecting uni to be disconnected.\n"); + errs++; + } + + // Check that a failed read/write reports DC + DCSocketTransport dctrans; + Session dctest; + dctest.Init(&dctrans); + if (!dctest.Connected()) { + printf("Expecting dctest to be connected.\n"); + errs++; + } + dctest.GetPacket(&pktIn); + if (dctest.Connected()) { + printf("Expecting dctest to be disconnected.\n"); + errs++; + } + + errs += gold.errs(); + return errs; +} + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/target.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/target.cc new file mode 100644 index 0000000..fe36e95 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/target.cc @@ -0,0 +1,636 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "native_client/src/debug_server/gdb_rsp/abi.h" +#include "native_client/src/debug_server/gdb_rsp/packet.h" +#include "native_client/src/debug_server/gdb_rsp/target.h" +#include "native_client/src/debug_server/gdb_rsp/session.h" +#include "native_client/src/debug_server/gdb_rsp/util.h" + +#include "native_client/src/debug_server/port/platform.h" +#include "native_client/src/debug_server/port/thread.h" + +#ifdef WIN32 +#define snprintf sprintf_s +#endif + +using std::string; + +using port::IEvent; +using port::IMutex; +using port::IPlatform; +using port::IThread; +using port::MutexLock; + +namespace gdb_rsp { + + +Target::Target(const Abi* abi) + : abi_(abi), + mutex_(NULL), + sig_start_(NULL), + sig_done_(NULL), + send_done_(false), + ctx_(NULL), + cur_signal_(-1), + sig_thread_(0), + run_thread_(-1), + reg_thread_(-1) { + if (NULL == abi_) abi_ = Abi::Get(); +} + +Target::~Target() { + Destroy(); +} + +bool Target::Init() { + string targ_xml = "l<target><architecture>"; + + targ_xml += abi_->GetName(); + targ_xml += "</architecture></target>"; + + // Set a more specific result which won't change. + properties_["target.xml"] = targ_xml; + properties_["Supported"] = + "PacketSize=7cf;qXfer:libraries:read+;qXfer:features:read+"; + + mutex_ = IMutex::Allocate(); + sig_start_ = IEvent::Allocate(); + sig_done_ = IEvent::Allocate(); + ctx_ = new uint8_t[abi_->GetContextSize()]; + + if ((NULL == mutex_) || (NULL == sig_start_) || (NULL == sig_done_) + || (NULL == ctx_)) { + Destroy(); + return false; + } + + // Allow one exception to happen + sig_start_->Signal(); + return true; +} + +void Target::Destroy() { + if (mutex_) IMutex::Free(mutex_); + if (sig_start_) IEvent::Free(sig_start_); + if (sig_done_) IEvent::Free(sig_done_); + + delete[] ctx_; +} + +bool Target::AddTemporaryBreakpoint(uint64_t address) { + const Abi::BPDef *bp = abi_->GetBreakpointDef(); + + // If this ABI does not support breakpoints then fail + if (NULL == bp) return false; + + // If we alreay have a breakpoint here then don't add it + BreakMap_t::iterator itr = breakMap_.find(address); + if (itr != breakMap_.end()) return false; + + uint8_t *data = new uint8_t[bp->size_]; + if (NULL == data) return false; + + // Copy the old code from here + if (IPlatform::GetMemory(address, bp->size_, data) == false) { + delete[] data; + return false; + } + if (IPlatform::SetMemory(address, bp->size_, bp->code_) == false) { + delete[] data; + return false; + } + + breakMap_[address] = data; + return true; +} + +bool Target::RemoveTemporaryBreakpoints() { + const Abi::BPDef *bp = abi_->GetBreakpointDef(); + + // Iterate through the map, removing breakpoints + while (!breakMap_.empty()) { + // Copy the key/value locally + BreakMap_t::iterator cur = breakMap_.begin(); + uint64_t addr = cur->first; + uint8_t *data = cur->second; + + // Then remove it from the map + breakMap_.erase(cur); + + // Copy back the old code, and free the data + IPlatform::SetMemory(addr, bp->size_, data); + delete[] data; + } + + return true; +} + + + +void Target::Signal(uint32_t id, int8_t sig, bool wait) { + // Wait for this signal's turn in the signal Q. + sig_start_->Wait(); + { + // Now lock the target, sleeping all active threads + MutexLock lock(mutex_); + + // Suspend all threads except this one + uint32_t curId; + bool more = GetFirstThreadId(&curId); + while (more) { + if (curId != id) { + IThread *thread = threads_[curId]; + thread->Suspend(); + } + more = GetNextThreadId(&curId); + } + + // Signal the stub (Run thread) that we are ready to process + // a trap, by updating the signal information and releasing + // the lock. + reg_thread_ = id; + run_thread_ = id; + cur_signal_ = sig; + } + + // Wait for permission to continue + if (wait) sig_done_->Wait(); +} + +void Target::Run(Session *ses) { + bool first = true; + do { + // Give everyone else a chance to use the lock + IPlatform::Relinquish(100); + + // Lock to prevent anyone else from modifying threads + // or updating the signal information. + MutexLock lock(mutex_); + Packet recv, reply; + + uint32_t id = 0; + + // If no signal is waiting for this iteration... + if (-1 == cur_signal_) { + // but the debugger is talking to us then force a break + if (ses->DataAvailable()) { + // set signal to 0 to signify paused + cur_signal_ = 0; + + // put all the threads to sleep. + uint32_t curId; + bool more = GetFirstThreadId(&curId); + while (more) { + if (curId != id) { + IThread *thread = threads_[curId]; + thread->Suspend(); + } + more = GetNextThreadId(&curId); + } + } else { + // otherwise, nothing to do so try again. + continue; + } + } else { + // otherwise there really is an exception so get the id of the thread + id = GetRegThreadId(); + } + + // If we got this far, then there is some kind of signal. + // So first, remove the breakpoints + RemoveTemporaryBreakpoints(); + + // Next update the current thread info + char tmp[16]; + snprintf(tmp, sizeof(tmp), "QC%x", id); + properties_["C"] = tmp; + + if (first) { + // First time on a connection, we don't sent the signal + first = false; + } else { + // All other times, send the signal that triggered us + Packet pktOut; + pktOut.AddRawChar('S'); + pktOut.AddWord8(cur_signal_); + ses->SendPacketOnly(&pktOut); + } + + // Now we are ready to process commands + // Loop through packets until we process a continue + // packet. + do { + if (ses->GetPacket(&recv)) { + reply.Clear(); + if (ProcessPacket(&recv, &reply)) { + // If this is a continue command, break out of this loop + break; + } else { + // Othwerise send the reponse + ses->SendPacket(&reply); + } + } + } while (ses->Connected()); + + + // Now that we are done, we want to continue in the "correct order". + // This means letting the active thread go first, in case we are single + // stepping and want to catch it again. This is a desired behavior but + // it is not guaranteed since another thread may already be in an + // exception state and next in line to notify the target. + + // If the run thread is not the exception thread, wake it up now. + uint32_t run_thread = GetRunThreadId(); + if (run_thread != id + && run_thread != static_cast<uint32_t>(-1)) { + IThread* thread = threads_[run_thread]; + thread->Resume(); + } + + // Next, wake up the exception thread, if there is one and it needs + // to wake up. + if (id && send_done_) sig_done_->Signal(); + + // Now wake up everyone else + uint32_t curId; + bool more = GetFirstThreadId(&curId); + while (more) { + if ((curId != id) && (curId != GetRunThreadId())) { + IThread *thread = threads_[curId]; + thread->Resume(); + } + more = GetNextThreadId(&curId); + } + + // Reset the signal value + cur_signal_ = -1; + + // If we took an exception, let the handler resume and allow + // the next exception to come in. + if (cur_signal_) { + sig_done_->Signal(); + sig_start_->Signal(); + } + + // Continue running until the connection is lost. + } while (ses->Connected()); +} + + + + +bool Target::GetFirstThreadId(uint32_t *id) { + threadItr_ = threads_.begin(); + return GetNextThreadId(id); +} + +bool Target::GetNextThreadId(uint32_t *id) { + if (threadItr_ == threads_.end()) return false; + + *id = (*threadItr_).first; + threadItr_++; + + return true; +} + + + +bool Target::ProcessPacket(Packet* pktIn, Packet* pktOut) { + char cmd; + int32_t seq = -1; + ErrDef err = NONE; + + // Clear the outbound message + pktOut->Clear(); + + // Pull out the sequence. + pktIn->GetSequence(&seq); + if (seq != -1) pktOut->SetSequence(seq); + + // Find the command + pktIn->GetRawChar(&cmd); + + switch (cmd) { + // IN : $? + // OUT: $Sxx + case '?': + pktOut->AddRawChar('S'); + pktOut->AddWord8(cur_signal_); + break; + + // IN : $d + // OUT: -NONE- + case 'd': + Detach(); + break; + + // IN : $g + // OUT: $xx...xx + case 'g': { + uint32_t id = GetRegThreadId(); + if (0 == id) { + err = BAD_ARGS; + break; + } + + IThread *thread = GetThread(id); + if (NULL == thread) { + err = BAD_ARGS; + break; + } + + // Copy OS preserved registers to GDB payload + for (uint32_t a = 0; a < abi_->GetRegisterCount(); a++) { + const Abi::RegDef *def = abi_->GetRegisterDef(a); + thread->GetRegister(a, &ctx_[def->offset_], def->bytes_); + } + + pktOut->AddBlock(ctx_, abi_->GetContextSize()); + break; + } + + // IN : $Gxx..xx + // OUT: $OK + case 'G': { + uint32_t id = GetRegThreadId(); + if (0 == id) { + err = BAD_ARGS; + break; + } + + IThread *thread = threads_[id]; + if (NULL == thread) { + err = BAD_ARGS; + break; + } + + // GDB payload to OS registers + for (uint32_t a = 0; a < abi_->GetRegisterCount(); a++) { + const Abi::RegDef *def = abi_->GetRegisterDef(a); + thread->SetRegister(a, &ctx_[def->offset_], def->bytes_); + } + pktOut->AddBlock(ctx_, abi_->GetContextSize()); + break; + } + + // IN : $H(c/g)(-1,0,xxxx) + // OUT: $OK + case 'H': { + char type; + uint64_t id; + + if (!pktIn->GetRawChar(&type)) { + err = BAD_FORMAT; + break; + } + if (!pktIn->GetNumberSep(&id, 0)) { + err = BAD_FORMAT; + break; + } + + if (threads_.begin() == threads_.end()) { + err = BAD_ARGS; + break; + } + + // If we are using "any" get the first thread + if (id == static_cast<uint64_t>(-1)) id = threads_.begin()->first; + + // Verify that we have the thread + if (threads_.find(static_cast<uint32_t>(id)) == threads_.end()) { + err = BAD_ARGS; + break; + } + + pktOut->AddString("OK"); + switch (type) { + case 'g': + reg_thread_ = static_cast<uint32_t>(id); + break; + + case 'c': + run_thread_ = static_cast<uint32_t>(id); + break; + + default: + err = BAD_ARGS; + break; + } + break; + } + + // IN : $maaaa,llll + // OUT: $xx..xx + case 'm': { + uint64_t addr; + uint64_t wlen; + uint32_t len; + if (!pktIn->GetNumberSep(&addr, 0)) { + err = BAD_FORMAT; + break; + } + + if (!pktIn->GetNumberSep(&wlen, 0)) { + err = BAD_FORMAT; + break; + } + + len = static_cast<uint32_t>(wlen); + uint8_t *block = new uint8_t[len]; + if (!port::IPlatform::GetMemory(addr, len, block)) err = FAILED; + + pktOut->AddBlock(block, len); + break; + } + + // IN : $Maaaa,llll:xx..xx + // OUT: $OK + case 'M': { + uint64_t addr; + uint64_t wlen; + uint32_t len; + + if (!pktIn->GetNumberSep(&addr, 0)) { + err = BAD_FORMAT; + break; + } + if (!pktIn->GetNumberSep(&wlen, 0)) { + err = BAD_FORMAT; + break; + } + + len = static_cast<uint32_t>(wlen); + uint8_t *block = new uint8_t[len]; + pktIn->GetBlock(block, len); + + if (!port::IPlatform::SetMemory(addr, len, block)) err = FAILED; + + pktOut->AddString("OK"); + break; + } + + case 'q': { + string tmp; + const char *str = &pktIn->GetPayload()[1]; + stringvec toks = StringSplit(str, ":;"); + PropertyMap_t::const_iterator itr = properties_.find(toks[0]); + + // If this is a thread query + if (!strcmp(str, "fThreadInfo") || !strcmp(str, "sThreadInfo")) { + uint32_t curr; + bool more = false; + if (str[0] == 'f') { + more = GetFirstThreadId(&curr); + } else { + more = GetNextThreadId(&curr); + } + + if (!more) { + pktOut->AddString("l"); + } else { + pktOut->AddString("m"); + pktOut->AddNumberSep(curr, 0); + } + break; + } + + // Check for architecture query + tmp = "Xfer:features:read:target.xml"; + if (!strncmp(str, tmp.data(), tmp.length())) { + stringvec args = StringSplit(&str[tmp.length()+1], ","); + if (args.size() != 2) break; + + const char *out = properties_["target.xml"].data(); + int offs = strtol(args[0].data(), NULL, 16); + int max = strtol(args[1].data(), NULL, 16) + offs; + int len = static_cast<int>(strlen(out)); + + if (max >= len) max = len; + + while (offs < max) { + pktOut->AddRawChar(out[offs]); + offs++; + } + break; + } + + // Check the property cache + if (itr != properties_.end()) { + pktOut->AddString(itr->second.data()); + } + break; + } + + case 'T': { + uint64_t id; + if (!pktIn->GetNumberSep(&id, 0)) { + err = BAD_FORMAT; + break; + } + + if (GetThread(static_cast<uint32_t>(id)) == NULL) { + err = BAD_ARGS; + break; + } + + pktOut->AddString("OK"); + break; + } + + case 's': { + IThread *thread = GetThread(GetRunThreadId()); + if (thread) thread->SetStep(true); + return true; + } + + case 'c': + return true; + + default: { + // If the command is not recognzied, ignore it by sending an + // empty reply. + string str; + pktIn->GetString(&str); + port::IPlatform::LogError("Unknown command: %s", str.data()); + return false; + } + } + + // If there is an error, return the error code instead of a payload + if (err) { + pktOut->Clear(); + pktOut->AddRawChar('E'); + pktOut->AddWord8(err); + } + return false; +} + + +void Target::TrackThread(IThread* thread) { + uint32_t id = thread->GetId(); + mutex_->Lock(); + threads_[id] = thread; + mutex_->Unlock(); +} + +void Target::IgnoreThread(IThread* thread) { + uint32_t id = thread->GetId(); + mutex_->Lock(); + ThreadMap_t::iterator itr = threads_.find(id); + + if (itr != threads_.end()) threads_.erase(itr); + mutex_->Lock(); +} + + +void Target::Detach() { + port::IPlatform::LogInfo("Requested Detach.\n"); +} + + +uint32_t Target::GetRegThreadId() const { + ThreadMap_t::const_iterator itr; + + switch (reg_thread_) { + // If we wany "any" then try the signal'd thread first + case 0: + case 0xFFFFFFFF: + itr = threads_.begin(); + break; + + default: + itr = threads_.find(reg_thread_); + break; + } + + if (itr == threads_.end()) return 0; + + return itr->first; +} + +uint32_t Target::GetRunThreadId() const { + return run_thread_; +} + +IThread* Target::GetThread(uint32_t id) { + ThreadMap_t::const_iterator itr; + itr = threads_.find(id); + if (itr != threads_.end()) return itr->second; + + return NULL; +} + + +} // namespace gdb_rsp + + + + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/target.h b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/target.h new file mode 100644 index 0000000..24f93f0 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/target.h @@ -0,0 +1,159 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + + +// This module provides interfaces for accessing the debugging state of +// the target. The target can use either the thread that took the +// exception or run in it's own thread. To respond to the host, the +// application must call the run function with a valid Transport +// which will then be polled for commands. The target will return +// from Run when the host disconnects, or requests a continue. +// +// The object is protected by a mutex, so that it is legal to track or +// ignore threads as an exception takes place. +// +// The Run function expects that all threads of interest are stopped +// with the Step flag cleared before Run is called. It is expected that +// and that all threads are updated with thier modified contexts and +// restarted when the target returns from Run. + +#ifndef NATIVE_CLIENT_GDB_RSP_TARGET_H_ +#define NATIVE_CLIENT_GDB_RSP_TARGET_H_ 1 + +#include <map> +#include <string> + +#include "native_client/src/debug_server/gdb_rsp/util.h" + +#include "native_client/src/debug_server/port/event.h" +#include "native_client/src/debug_server/port/platform.h" +#include "native_client/src/debug_server/port/mutex.h" +#include "native_client/src/debug_server/port/thread.h" + +namespace gdb_rsp { + +class Abi; +class Packet; +class Session; + +class Target { + public: + enum ErrDef { + NONE = 0, + BAD_FORMAT = 1, + BAD_ARGS = 2, + FAILED = 3 + }; + + enum State { + UNINIT = 0, + RUNNING = 1, + STOPPED = 2 + }; + + typedef ErrDef (*QFunc_t)(Target *, stringvec&, std::string); + typedef std::map<uint32_t, port::IThread*> ThreadMap_t; + typedef std::map<std::string, std::string> PropertyMap_t; + typedef std::map<uint64_t, uint8_t*> BreakMap_t; + + public: + // Contruct a Target object. By default use the native ABI. + explicit Target(const Abi *abi = NULL); + ~Target(); + + // Init must be the first function called to correctlty + // build the Target internal structures. + bool Init(); + + // Add and remove temporary breakpoints. These breakpoints + // must be added just before we start running, and removed + // just before we stop running to prevent the debugger from + // seeing the modified memory. + bool AddTemporaryBreakpoint(uint64_t address); + bool RemoveTemporaryBreakpoints(); + + // This function should be called by a tracked thread when it takes + // an exception. It takes sig_start_ to prevent other exceptions + // from signalling thread. If wait is true, it will then block on + // sig_done_ until a continue is issued by the host. + void Signal(uint32_t id, int8_t sig, bool wait); + + // This function will spin on a session, until it closes. If an + // exception is caught, it will signal the exception thread by + // setting sig_done_. + void Run(Session *ses); + + // This function causes the target to track the state + // of the specified thread and make it availible to + // a connected host. + void TrackThread(port::IThread *thread); + + // This function causes the target to stop tracking the + // state of the specified thread, which will no longer + // be visible to the host. + void IgnoreThread(port::IThread *thread); + + protected: + // This function always succeedes, since all errors + // are reported as an error string of "E<##>" where + // the two digit number. The error codes are not + // not documented, so this implementation uses + // ErrDef as errors codes. This function returns + // true a request to continue (or step) is processed. + bool ProcessPacket(Packet *pktIn, Packet *pktOut); + + void Destroy(); + void Detach(); + + bool GetFirstThreadId(uint32_t *id); + bool GetNextThreadId(uint32_t *id); + + uint32_t GetRegThreadId() const; + uint32_t GetRunThreadId() const; + port::IThread *GetThread(uint32_t id); + + public: + const Abi *abi_; + + // This mutex protects debugging state (threads_, cur_signal, sig_thread_) + port::IMutex *mutex_; + + // This event is signalled when the target is really to process an + // exception. It ensures only one exception is processed at a time. + port::IEvent *sig_start_; + + // This event is signalled when the target done processing an exception. + port::IEvent *sig_done_; + + // This value is set if the exception cather is requesting a continue signal + bool send_done_; + + ThreadMap_t threads_; + ThreadMap_t::const_iterator threadItr_; + BreakMap_t breakMap_; + + + PropertyMap_t properties_; + + uint8_t *ctx_; // Context Scratchpad + + // The current signal and signaling thread data is protected by + // the mutex, and can only be owned by one thread at a time. + // These values should only be written via the "Signal" member, + // which will ensure that a new signal does not overwrite a + // previous signal. + volatile int8_t cur_signal_; + volatile uint32_t sig_thread_; + + uint32_t run_thread_; // Which thread to issue step commands on + uint32_t reg_thread_; // Which thread to issue context (reg) commands on +}; + + +} // namespace gdb_rsp + +#endif // NATIVE_CLIENT_GDB_RSP_TARGET_H_ + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/target_test.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/target_test.cc new file mode 100644 index 0000000..75a63bc --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/target_test.cc @@ -0,0 +1,61 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <string> +#include <sstream> +#include <vector> + +#include "native_client/src/debug_server/gdb_rsp/abi.h" +#include "native_client/src/debug_server/gdb_rsp/session_mock.h" +#include "native_client/src/debug_server/gdb_rsp/target.h" +#include "native_client/src/debug_server/gdb_rsp/test.h" + +using gdb_rsp::Abi; +using gdb_rsp::SessionMock; +using gdb_rsp::Target; + +int TestTarget() { + int errs = 0; + + SessionMock *ses = gdb_rsp::GetGoldenSessionMock(true, false); + const gdb_rsp::Abi* abi = gdb_rsp::Abi::Get(); + + Target *target = new Target(NULL); + if (!target->Init()) { + printf("Failed to INIT target.\n"); + return 1; + } + + // Create a pseudo thread with registers set to their index + port::IThread *thread = port::IThread::Acquire(0x1234, true); + for (uint32_t a = 0; a < abi->GetRegisterCount(); a++) { + const Abi::RegDef* def = abi->GetRegisterDef(a); + uint64_t val = static_cast<uint64_t>(a); + thread->SetRegister(a, &val, def->bytes_); + } + target->TrackThread(thread); + + // Pretend we just got a signal on that thread + target->Signal(thread->GetId(), 5, false); + + // Run the session to completion. + target->Run(ses); + + if (ses->PeekAction() != SessionMock::DISCONNECT) { + printf("We did not consume all the actions.\n"); + errs++; + } + + delete target; + delete ses; + return errs; +} + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/test.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/test.cc new file mode 100644 index 0000000..83a4c7c --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/test.cc @@ -0,0 +1,152 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "native_client/src/debug_server/gdb_rsp/test.h" +#include "native_client/src/debug_server/port/platform.h" + +// Mock portability objects +namespace port { +void IPlatform::Relinquish(uint32_t msec) { + (void) msec; + return; +} + +void IPlatform::LogInfo(const char *fmt, ...) { + va_list argptr; + va_start(argptr, fmt); + + vprintf(fmt, argptr); +} + +void IPlatform::LogWarning(const char *fmt, ...) { + va_list argptr; + va_start(argptr, fmt); + + vprintf(fmt, argptr); +} + +void IPlatform::LogError(const char *fmt, ...) { + va_list argptr; + va_start(argptr, fmt); + + vprintf(fmt, argptr); +} + +// The unit tests are singly threaded, so we just do nothing +// for synchronization +class MutexMock : public IMutex { + void Lock() {} + void Unlock() {} + bool Try() { return true; } +}; + +IMutex* IMutex::Allocate() { return new MutexMock; } +void IMutex::Free(IMutex* mtx) { delete static_cast<MutexMock*>(mtx); } + +class EventMock : public IEvent { + void Signal() {} + void Wait() {} +}; + +IEvent* IEvent::Allocate() { return new EventMock; } +void IEvent::Free(IEvent* e) { delete static_cast<EventMock*>(e); } + +class ThreadMock : public IThread { + public: + explicit ThreadMock(uint32_t id) { + id_ = id; + ctx_ = new uint8_t[gdb_rsp::Abi::Get()->GetContextSize()]; + } + ~ThreadMock() { delete[] ctx_; } + + uint32_t GetId() { return id_; } + State GetState() { return IThread::SIGNALED; } + + bool SetStep(bool on) { + (void) on; + return true; + } + + bool GetRegister(uint32_t index, void *dst, uint32_t len) { + const gdb_rsp::Abi* abi = gdb_rsp::Abi::Get(); + const gdb_rsp::Abi::RegDef *reg = abi->GetRegisterDef(index); + memcpy(dst, ctx_ + reg->offset_, len); + return true; + } + + bool SetRegister(uint32_t index, void *src, uint32_t len) { + const gdb_rsp::Abi* abi = gdb_rsp::Abi::Get(); + const gdb_rsp::Abi::RegDef *reg = abi->GetRegisterDef(index); + memcpy(ctx_ + reg->offset_, src, len); + return true; + } + + bool Suspend() { return true; } + bool Resume() { return true; } + + virtual void *GetContext() { return ctx_; } + + private: + uint8_t *ctx_; + uint32_t id_; +}; + +IThread* IThread::Acquire(uint32_t id, bool create) { + if (create) return new ThreadMock(id); + return NULL; +} + + +bool port::IPlatform::GetMemory(uint64_t addr, uint32_t len, void *dst) { + intptr_t iptr = static_cast<intptr_t>(addr); + void *src = reinterpret_cast<void*>(iptr); + memcpy(dst, src, len); + return true; +} + +bool port::IPlatform::SetMemory(uint64_t addr, uint32_t len, void *src) { + intptr_t iptr = static_cast<intptr_t>(addr); + void *dst = reinterpret_cast<void*>(iptr); + memcpy(dst, src, len); + return true; +} + + +} // End of namespace port + +int main(int argc, const char *argv[]) { + int errs = 0; + + (void) argc; + (void) argv; + + printf("Testing Utils.\n"); + errs += TestUtil(); + + printf("Testing ABI.\n"); + errs += TestAbi(); + + printf("Testing Packets.\n"); + errs += TestPacket(); + + printf("Testing Session.\n"); + errs += TestSession(); + + printf("Testing Host.\n"); + errs += TestHost(); + + printf("Testing Target.\n"); + errs += TestTarget(); + + if (errs) printf("FAILED with %d errors.\n", errs); + return errs; +} + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/test.h b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/test.h new file mode 100644 index 0000000..ae0b32b --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/test.h @@ -0,0 +1,37 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ +#ifndef NATIVE_CLIENT_GDB_RSP_TEST_H_ +#define NATIVE_CLIENT_GDB_RSP_TEST_H_ 1 + +#include <vector> + +#include "native_client/src/debug_server/port/std_types.h" +#include "native_client/src/debug_server/port/platform.h" +#include "native_client/src/debug_server/port/transport.h" + +#include "native_client/src/debug_server/gdb_rsp/abi.h" +#include "native_client/src/debug_server/gdb_rsp/host.h" +#include "native_client/src/debug_server/gdb_rsp/packet.h" +#include "native_client/src/debug_server/gdb_rsp/session.h" +#include "native_client/src/debug_server/gdb_rsp/target.h" +#include "native_client/src/debug_server/gdb_rsp/util.h" + +typedef void (*PacketFunc_t)(void *ctx, + gdb_rsp::Packet *wr, + gdb_rsp::Packet *rd); + +int VerifyPacket(gdb_rsp::Packet *wr, gdb_rsp::Packet *rd, + void *ctx, PacketFunc_t tx); + +int TestAbi(); +int TestHost(); +int TestPacket(); +int TestSession(); +int TestTarget(); +int TestUtil(); + +#endif // NATIVE_CLIENT_GDB_RSP_TEST_H_ + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/util.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/util.cc new file mode 100644 index 0000000..df752ae --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/util.cc @@ -0,0 +1,125 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +#include <string> +#include <vector> + +#include "native_client/src/debug_server/gdb_rsp/util.h" +#include "native_client/src/debug_server/port/std_types.h" + +using std::string; + +namespace gdb_rsp { + +bool NibbleToInt(char ch, int *val) { + // Check for nibble of a-f + if ((ch >= 'a') && (ch <= 'f')) { + if (val) *val = (ch - 'a' + 10); + return true; + } + + // Check for nibble of A-F + if ((ch >= 'A') && (ch <= 'F')) { + if (val) *val = (ch - 'A' + 10); + return true; + } + + // Check for nibble of 0-9 + if ((ch >= '0') && (ch <= '9')) { + if (val) *val = (ch - '0'); + return true; + } + + // Not a valid nibble representation + return false; +} + +bool IntToNibble(int nibble, char *ch) { + // Verify this value fits in a nibble + if (nibble != (nibble & 0xF)) return false; + + nibble &= 0xF; + if (nibble < 10) { + if (ch) *ch = '0' + nibble; + } else { + // Although uppercase maybe more readible GDB + // expects lower case values. + if (ch) *ch = 'a' + (nibble - 10); + } + + return true; +} + +bool NibblesToByte(const char *inStr, int *outInt) { + int o1, o2; + + if (NibbleToInt(inStr[0], &o1) && NibbleToInt(inStr[1], &o2)) { + *outInt = (o1 << 4) + o2; + return true; + } + + return false; +} + +#ifdef WIN32 +int snprintf(char *buf, size_t size, const char *fmt, ...) { + va_list argptr; + va_start(argptr, fmt); + + int len = vsnprintf(buf, size, fmt, argptr); + return len; +} +#endif + +stringvec StringSplit(const string &instr, const char *delim) { + int count = 0; + const char *in = instr.data(); + const char *start = in; + + stringvec ovec; + + // Check if we have nothing to do + if (NULL == in) return ovec; + if (NULL == delim) { + ovec.push_back(string(in)); + return ovec; + } + + while (*in) { + int len = 0; + // Toss all preceeding delimiters + while (*in && strchr(delim, *in)) in++; + + // If we still have something to process + if (*in) { + std::string token; + start = in; + len = 0; + // Keep moving forward for all valid chars + while (*in && (strchr(delim, *in) == NULL)) { + len++; + in++; + } + + // Build this token and add it to the array. + ovec.resize(count + 1); + ovec[count].assign(start, len); + count++; + } + } + + return ovec; +} + + + +} // End of namespace gdb_rsp + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/util.h b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/util.h new file mode 100644 index 0000000..2f18f4e --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/util.h @@ -0,0 +1,41 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#ifndef NATIVE_CLIENT_GDB_RSP_UTIL_H_ +#define NATIVE_CLIENT_GDB_RSP_UTIL_H_ 1 + +#include <sstream> +#include <string> +#include <vector> + +#include "native_client/src/debug_server/port/std_types.h" + +namespace gdb_rsp { + +typedef std::vector<std::string> stringvec; + +// Convert from ASCII (0-9,a-f,A-F) to 4b unsigned or return +// false if the input char is unexpected. +bool NibbleToInt(char inChar, int *outInt); + +// Convert from 0-15 to ASCII (0-9,a-f) or return false +// if the input is not a value from 0-15. +bool IntToNibble(int inInt, char *outChar); + +// Convert a pair of nibbles to a value from 0-255 or return +// false if ethier input character is not a valid nibble. +bool NibblesToByte(const char *inStr, int *outInt); + +#ifdef WIN32 +int snprintf(char *str, size_t size, const char *fmt, ...); +#endif + +stringvec StringSplit(const std::string& instr, const char *delim); + +} // namespace gdb_rsp + +#endif // NATIVE_CLIENT_GDB_RSP_UTIL_H_ + diff --git a/native_client_sdk/src/build_tools/debug_server/gdb_rsp/util_test.cc b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/util_test.cc new file mode 100644 index 0000000..15f3dfe --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/gdb_rsp/util_test.cc @@ -0,0 +1,74 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <string> + +#include "native_client/src/debug_server/gdb_rsp/util.h" + +using std::string; +using gdb_rsp::NibbleToInt; +using gdb_rsp::IntToNibble; +using gdb_rsp::stringvec; + + +int TestUtil() { + char goodStr[] = "0123456789abcdefABCDEF"; + int good; + int val; + int a; + int errCnt = 0; + + // Make sure we find expected and only expected conversions (0-9,a-f,A-F) + good = 0; + for (a = 0; a < 256; a++) { + // NOTE: Exclude 'zero' since the terminator is found by strchr + bool found = a && (strrchr(goodStr, a) != NULL); + bool xvert = NibbleToInt(static_cast<char>(a), &val); + if (xvert != found) { + printf("FAILED NibbleToInt of '%c'(#%d), convertion %s.\n", + a, a, xvert ? "succeeded" : "failed"); + errCnt++; + } + } + + good = 0; + for (a = -256; a < 256; a++) { + char ch; + bool xvert = IntToNibble(a, &ch); + + if (xvert) { + good++; + if (!NibbleToInt(ch, &val)) { + printf("FAILED IntToNibble on good NibbleToInt of #%d\n.\n", a); + errCnt++; + } + // NOTE: check if IntToNible matches NibbleToInt looking at both + // possitive and negative values of -15 to +15 + if (val != a) { + printf("FAILED IntToNibble != NibbleToInt of #%d\n.\n", a); + errCnt++; + } + } + } + + // Although we check -15 to +15 above, we realy only want -7 to +15 + // to verify unsiged (0-15) plus signed (-7 to -1). + if (good != 16) { + printf("FAILED IntToNibble range of 0 to 15.\n"); + errCnt++; + } + + string xml = "qXfer:features:read:target.xml:0,7ca"; + string str; + stringvec vec, vec2; + + return errCnt; +} + diff --git a/native_client_sdk/src/build_tools/debug_server/nacl_debug.cc b/native_client_sdk/src/build_tools/debug_server/nacl_debug.cc new file mode 100644 index 0000000..6ecd89d --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/nacl_debug.cc @@ -0,0 +1,322 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + + +#include <vector> +#include <map> + +/* + * NaCl Functions for intereacting with debuggers + */ + +#include "native_client/src/debug_server/gdb_rsp/session.h" +#include "native_client/src/debug_server/gdb_rsp/target.h" +#include "native_client/src/debug_server/port/platform.h" +#include "native_client/src/debug_server/port/thread.h" + +#include "native_client/src/include/nacl_string.h" +#include "native_client/src/shared/platform/nacl_log.h" +#include "native_client/src/shared/platform/nacl_threads.h" +#include "native_client/src/debug_server/debug_stub/debug_stub.h" +#include "native_client/src/trusted/service_runtime/nacl_app_thread.h" +#include "native_client/src/debug_server/nacl_debug.h" + +#include "native_client/src/trusted/service_runtime/sel_ldr.h" + +using port::IPlatform; +using port::IThread; +using port::ITransport; + +using gdb_rsp::Session; +using gdb_rsp::Target; + +#ifdef WIN32 +/* Disable warning for unwind disabled when exceptions used */ +#pragma warning(disable:4530) +#endif + +/* + * These macro wraps all debugging stub calls to prevent C++ code called + * by the debugging stub to throw and exception past the C API. We use + * this technique to allow the use of STL templates. We catch bad_alloc + * seperately purely to provide information for debugging purposes. + */ +#define DBG_CATCH_ALL \ + catch(std::bad_alloc) { \ + NaClLog(LOG_FATAL, "nacl_debug(%d) : Failed to allocate.\n", __LINE__); \ + exit(-1); \ + } \ + catch(std::exception e) { \ + NaClLog(LOG_FATAL, "nacl_debug(%d) : Caught exception: %s.\n", \ + __LINE__ , e.what()); \ + exit(-1); \ + } \ + catch(...) { \ + NaClLog(LOG_FATAL, "nacl_debug(%d) : Unexpected exception.\n", __LINE__);\ + exit(-1); \ + } + + +enum NaClDebugStatus { + NDS_DISABLED = 0, + NDS_ENABLED = 1, + NDS_STOPPED = 2 +}; + +/* + * Remove name mangling to make it easier to find, incase we want to toggle + * internal stub debugging from an external debugger. + */ +extern "C" { + uint32_t nacl_debug_allowed = 0; +} + +struct NaClDebugState { + NaClDebugState() : target_(NULL), app_(NULL), break_(false), + errCode_(0), status_(NDS_DISABLED) { +#ifdef _DEBUG + /* + * When compiling DEBUG we allow an environment variable to enable + * debugging, otherwise debugging could be allowed on a release + * build by modifying nacl_debug_allowed. + */ + if (NULL != getenv("NACL_DEBUG_ENABLE")) nacl_debug_allowed = 1; +#endif + status_ = nacl_debug_allowed ? NDS_ENABLED : NDS_DISABLED; + + /* Initialize the stub if and only if debugging is enabled. */ + if (status_) { + NaClDebugStubInit(); + target_ = new Target(); + + /* If we failed to initialize the target, turn off debugging */ + if ((NULL == target_) || (!target_->Init())) status_ = NDS_DISABLED; + } + } + + ~NaClDebugState() { + delete target_; + } + + Target* target_; + struct NaClApp *app_; + bool break_; + volatile int errCode_; + NaClDebugStatus status_; + nacl::string path_; + std::vector<const char *> arg_; + std::vector<const char *> env_; +}; + +/* + * NOTE: We use a singleton to delay construction allowing someone + * to enable debugging only before the first use of this object. + */ +static NaClDebugState *NaClDebugGetState() { + static NaClDebugState state; + return &state; +} + +void NaClDebugSetAllow(int enable) throw() { +#ifdef NACL_DEBUG_STUB + nacl_debug_allowed = enable; +#else + UNREFERENCED_PARAMETER(enable); +#endif +} + +void NaClDebugSetStartBroken(int enable) throw() { +#ifdef NACL_DEBUG_STUB + NaClDebugState* state = NaClDebugGetState(); + state->break_ = enable ? true : false; +#else + UNREFERENCED_PARAMETER(enable); +#endif +} + + + +void WINAPI NaClStubThread(void *ptr) { +#ifdef NACL_DEBUG_STUB + uint32_t id = static_cast<uint32_t>(GetCurrentThreadId()); + printf("---> NaClStubThread(void *ptr) thread id = %d == 0x%X\n", id, id); + + Target *targ = reinterpret_cast<Target*>(ptr); + while (1) { + ITransport* trans = NULL; + Session* ses = NULL; + + try { + // Wait for a connection. + printf("---> %d\n", __LINE__); fflush(stdout); + trans = ITransport::Accept("0.0.0.0:4014"); + printf("---> %d\n", __LINE__); fflush(stdout); + if (NULL == trans) continue; + printf("---> %d\n", __LINE__); fflush(stdout); + + // Create a new session for this connection + ses = new Session(); + ses->Init(trans); + ses->SetFlags(Session::DEBUG_MASK); + + // Run this session for as long as it lasts + targ->Run(ses); + } + catch(...) { + printf("---> %d\n", __LINE__); fflush(stdout); + delete ses; + printf("---> %d\n", __LINE__); fflush(stdout); + ITransport::Free(trans); + printf("---> %d\n", __LINE__); fflush(stdout); + } + printf("---> %d\n", __LINE__); fflush(stdout); + } +#else + UNREFERENCED_PARAMETER(ptr); +#endif +} + +void NaClExceptionCatcher(uint32_t id, int8_t sig, void *cookie) { +#ifdef NACL_DEBUG_STUB + Target* targ = static_cast<Target*>(cookie); + + /* Signal the target that we caught something */ + IPlatform::LogWarning("Caught signal %d on thread %Xh.\n", sig, id); + targ->Signal(id, sig, true); +#else + UNREFERENCED_PARAMETER(id); + UNREFERENCED_PARAMETER(sig); + UNREFERENCED_PARAMETER(cookie); +#endif +} + + +int NaClDebugIsEnabled(void) throw() { +#ifdef NACL_DEBUG_STUB + try { + return (NDS_ENABLED == NaClDebugGetState()->status_) ? 1 : 0; + } DBG_CATCH_ALL +#endif + + return false; +} + +void NaClDebugSetAppPath(const char *path) throw() { + try { + if (NaClDebugIsEnabled()) NaClDebugGetState()->path_ = path; + } DBG_CATCH_ALL +} + + +void NaClDebugSetAppInfo(struct NaClApp *app) throw() { +#ifdef NACL_DEBUG_STUB + if (NaClDebugIsEnabled()) { + NaClDebugState *state = NaClDebugGetState(); + state->app_ = app; + } +#else + UNREFERENCED_PARAMETER(app); +#endif +} + + +void NaClDebugSetAppEnvironment(int argc, char const * const argv[], + int envc, char const * const envv[]) throw() { + if (NaClDebugIsEnabled()) { + int a; + try { + /* + * Copy the pointer arrays. We use ptrs instead of strings + * since the data persits and it prevents an extra copy. + */ + NaClDebugGetState()->arg_.resize(argc); + for (a = 0; a < argc; a++) NaClDebugGetState()->arg_[a] = argv[a]; + NaClDebugGetState()->env_.resize(envc); + for (a = 0; a < envc; a++) NaClDebugGetState()->env_[a] = envv[a]; + } DBG_CATCH_ALL + } +} + +void NaClDebugThreadPrepDebugging(struct NaClAppThread *natp) throw() { + UNREFERENCED_PARAMETER(natp); + +#ifdef NACL_DEBUG_STUB + if (NaClDebugIsEnabled()) { + NaClDebugState *state = NaClDebugGetState(); + uint32_t id = IPlatform::GetCurrentThread(); + IThread* thread = IThread::Acquire(id, true); + state->target_->TrackThread(thread); + + /* + * TODO(noelallen) We need to associate the natp with this thread + * so we can get to the untrusted context preserved on a syscall. + */ + } +#endif +} + +void NaClDebugThreadStopDebugging(struct NaClAppThread *natp) throw() { + UNREFERENCED_PARAMETER(natp); + +#ifdef NACL_DEBUG_STUB + if (NaClDebugIsEnabled()) { + NaClDebugState *state = NaClDebugGetState(); + uint32_t id = IPlatform::GetCurrentThread(); + IThread* thread = IThread::Acquire(id, false); + state->target_->IgnoreThread(thread); + IThread::Release(thread); + + /* + * TODO(noelallen) We need to associate the natp with this thread + * so we can get to the thread once we support freeing a thread + * from a different thread than the executing one. + */ + } +#endif +} + + +int NaClDebugStart(void) throw() { +#ifdef NACL_DEBUG_STUB + if (NaClDebugIsEnabled()) { + NaClThread *thread = new NaClThread; + + if (NULL == thread) return false; + NaClDebugState *state = NaClDebugGetState(); + + /* If break on start has been requested, add a temp breakpoint. */ + if (state->break_) { + struct NaClApp* app = state->app_; + state->target_->AddTemporaryBreakpoint(app->entry_pt + app->mem_start); + } + + NaClLog(LOG_WARNING, "nacl_debug(%d) : Debugging started.\n", __LINE__); + IThread::SetExceptionCatch(NaClExceptionCatcher, state->target_); + return NaClThreadCtor(thread, NaClStubThread, state->target_, + NACL_KERN_STACK_SIZE); + } +#endif + return 0; +} + +void NaClDebugStop(int ErrCode) throw() { +#ifdef NACL_DEBUG_STUB + + /* + * We check if debugging is enabled since this check is the only + * mechanism for allocating the state object. We free the + * resources but not the object itself. Instead we mark it as + * STOPPED to prevent it from getting recreated. + */ + if (NaClDebugIsEnabled()) { + NaClDebugGetState()->status_ = NDS_STOPPED; + NaClDebugGetState()->errCode_ = ErrCode; + } +#else + UNREFERENCED_PARAMETER(ErrCode); +#endif +} + diff --git a/native_client_sdk/src/build_tools/debug_server/nacl_debug.h b/native_client_sdk/src/build_tools/debug_server/nacl_debug.h new file mode 100644 index 0000000..37a7b50 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/nacl_debug.h @@ -0,0 +1,113 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +/* + * This module provides a 'C' style interface to the NaCl debugging + * related functions, and drives the C++ debugging stub. The functions + * defined here are safe to call, because they always check if debugging + * is enabled and catch all exceptions throws by underlying C++ libraries. + * For this reason, the debug stub library should not be called directly. + * + * The behavior of the debug stub is controlled through a combination of + * a set of environment (described bellow), and conditional compilation + * based on definition of _DEBUG. + * + * These functions are non-reentrant except for the functions notifying + * the debug stub that thread has been created or is about to be + * destroyed. It is expected the other functions will all be called by + * the main thread. + */ + +#ifndef NATIVE_CLIENT_SERVICE_RUNTIME_NACL_DEBUG_H_ +#define NATIVE_CLIENT_SERVICE_RUNTIME_NACL_DEBUG_H_ + +#include "native_client/src/include/nacl_base.h" +#include "native_client/src/include/portability.h" + +EXTERN_C_BEGIN + +struct NaClApp; +struct NaClAppThread; + +/* + * Allows debugging if the feature has been enabled by compile flags. + * The conditional compile means setting this to non-zero only signals + * that debugging is requested, NaClDebugIsEnabled will need to be called + * to determine if debugging is actually enabled. + */ +void NaClDebugSetAllow(int val) NO_THROW; + +/* + * If debugging is enabled, setting this to true will cause the NEXE + * to break immediately to allow a debugger to attach. + */ +void NaClDebugSetStartBroken(int val) NO_THROW; + +/* + * Returns non-zero if debugging is allowed. This check is done via the + * NACL_DEBUG_ENABLE environment variable. + */ +//int NaClDebugIsEnabled(void) NO_THROW; //??? + +/* + * These functions should be called before the debug server has started + * to prevent a debugger from attaching and failing to retrieve + * information about the App (NEXE) that is running. These functions are + * safe to call even if debugging has not been enabled. + */ +//void NaClDebugSetAppPath(const char *path) NO_THROW; //??? +void NaClDebugSetAppInfo(struct NaClApp *app) NO_THROW; +void NaClDebugSetAppEnvironment(int argc, char const * const argv[], + int envc, char const * const envv[]) NO_THROW; + +/* + * This function must be called each time we start a new App thread by the + * by the untrusted thread itself as early as possible. The function will + * notifying the debuging stub of it's existance, and preparing the thread + * for signal or exception handling. For each thread that calls this + * function, if debugging is: + * + * ENABLED : all exceptions will be sent to the debug stub. + * DISABLED: all exceptions will be sent to breakpad. + */ +void NaClDebugThreadPrepDebugging(struct NaClAppThread *natp) NO_THROW; + + +/* + * This function notifies the debug stub that the provided thread + * should no longer be debugged. This is typically because the thread + * is about to halt. TODO(noelallen) Unlike "Prep", this function can + * be called by any thread. + */ +void NaClDebugThreadStopDebugging(struct NaClAppThread *natp) NO_THROW; + + +/* + * This function notifies the debug stub that the system is ready to + * allow debugging, and should finish preparation for debuggers to attach. + * The function will launch a new thread to act as a sever processing GDB + * RSP based connections. How a remote debugger connects to the debugging + * stub is controlled by the following environment variables: + * NACL_DEBUG_ENABLE - if defined, debugging is allowed + * NACL_DEBUG_IP - IPv4 address on which to bind (default "127.0.0.1") + * NACL_DEBUG_PORT - Port(s) on which to listen (default "8000:8010") + */ +int NaClDebugStart(void) NO_THROW; + + +/* + * Signals the debugging thread (if one has started), that it should + * release all debugging resources and halt. Prior to closing any + * connections, the debugging stub has the opportunity to notify + * an attached debugger (assuming it is in a state to receive the + * message) of the App exit code. + */ +void NaClDebugStop(int exitCode) NO_THROW; + + +EXTERN_C_END + +#endif /* NATIVE_CLIENT_SERVICE_RUNTIME_NACL_DEBUG_H_ */ diff --git a/native_client_sdk/src/build_tools/debug_server/port/event.h b/native_client_sdk/src/build_tools/debug_server/port/event.h new file mode 100644 index 0000000..cd6a28e --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/port/event.h @@ -0,0 +1,33 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + + +// This module defines the interface for using platform specific events. +// It is expected that the Allocation function will return NULL on any +// failure instead of throwing an exception. This module is expected +// to throw a std::exception when an unexpected OS error is encoutered. +#ifndef NATIVE_CLIENT_PORT_EVENT_H_ +#define NATIVE_CLIENT_PORT_EVENT_H_ 1 + +namespace port { + +class IEvent { + public: + virtual void Signal() = 0; // Free one waiting thread + virtual void Wait() = 0; // Suspend this thread waiting for signal + + static IEvent *Allocate(); // Allocate an IEvent + static void Free(IEvent *e); // Free the IEvent + + protected: + virtual ~IEvent() {} // Prevent delete of base pointer +}; + + +} // namespace port + +#endif // NATIVE_CLIENT_PORT_EVENT_H_ + diff --git a/native_client_sdk/src/build_tools/debug_server/port/mutex.h b/native_client_sdk/src/build_tools/debug_server/port/mutex.h new file mode 100644 index 0000000..44458bb6 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/port/mutex.h @@ -0,0 +1,61 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + + +// This module defines the interface for using platform specific mutexes. +// It is expected that the Allocation function will return NULL on any +// failure instead of throwing an exception. This module is expected +// to throw a std::exception when an unexpected OS error is encoutered. +// +// The mutex is owned by a single thread at a time. The thread that +// owns the mutex is free to call Lock multiple times, however it must +// call Unlock the same number of times to release the lock. +#ifndef NATIVE_CLIENT_PORT_MUTEX_H_ +#define NATIVE_CLIENT_PORT_MUTEX_H_ 1 + +#include <assert.h> +#include <stddef.h> + +namespace port { + +class IMutex { + public: + virtual void Lock() = 0; // Block until the mutex is taken + virtual void Unlock() = 0; // Unlock the mutext + virtual bool Try() = 0; // Try to lock, but return immediately + + static IMutex *Allocate(); // Allocate a mutex + static void Free(IMutex *mtx); // Free a mutex + + protected: + virtual ~IMutex() {} // Prevent delete of base pointer +}; + + +// MutexLock +// A MutexLock object will lock on construction and automatically +// unlock on destruction of the object as the object goes out of scope. +class MutexLock { + public: + explicit MutexLock(IMutex *mutex) : mutex_(mutex) { + assert(NULL != mutex_); + mutex_->Lock(); + } + ~MutexLock() { + mutex_->Unlock(); + } + + private: + IMutex *mutex_; + MutexLock(const MutexLock&); + MutexLock &operator=(const MutexLock&); +}; + + +} // namespace port + +#endif // NATIVE_CLIENT_PORT_MUTEX_H_ + diff --git a/native_client_sdk/src/build_tools/debug_server/port/platform.h b/native_client_sdk/src/build_tools/debug_server/port/platform.h new file mode 100644 index 0000000..b79103d0 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/port/platform.h @@ -0,0 +1,52 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + + +// This module defines the interface for platform specific support +// functions, such as thread creation, logging, exception catching, +// etc... This API is not expected to throw, and instead will return +// false on any function that can fail. Since this is a collection +// of helpers functions there is expected to be only one platform object +// which can be retrieved with the static "Get" member. +#ifndef NATIVE_CLIENT_PORT_PLATFORM_H_ +#define NATIVE_CLIENT_PORT_PLATFORM_H_ 1 + +#include "native_client/src/debug_server/port/std_types.h" +#include "native_client/src/debug_server/port/transport.h" + +namespace port { + +class IPlatform { + public: + typedef void (*ThreadFunc_t)(void *cookie); + + // Get the id of the currently executing thread + static uint32_t GetCurrentThread(); + + // Called to request the platform start/stop the thread + static uint32_t CreateThread(ThreadFunc_t func, void *cookie); + + // Request the current thread relinquish execution of msec milliseconds + static void Relinquish(uint32_t msec); + + // Called to get or set process memory. + // NOTE: These functions should change the protection of the underlying + // page if needed to provide access. It should only return false if + // the page is not mapped into the debugged process. + static bool GetMemory(uint64_t address, uint32_t length, void *dst); + static bool SetMemory(uint64_t address, uint32_t length, void *src); + + // Log a message + static void LogInfo(const char *fmt, ...); + static void LogWarning(const char *fmt, ...); + static void LogError(const char *fmt, ...); +}; + +} // namespace port + + +#endif // NATIVE_CLIENT_PORT_PLATFORM_H_ + diff --git a/native_client_sdk/src/build_tools/debug_server/port/port.gyp b/native_client_sdk/src/build_tools/debug_server/port/port.gyp new file mode 100644 index 0000000..a0d9dc2 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/port/port.gyp @@ -0,0 +1,54 @@ +# -*- python -*- +# Copyright 2009, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +{ + 'includes': [ + '../../src/build/common.gypi', + ], + 'variables': { + 'common_sources': [ + 'event.h', + 'mutex.h', + 'platform.h', + 'thread.h', + 'transport.h', + 'std_types.h', + ], + }, + 'targets': [ + { + 'target_name': 'port', + 'type': 'static_library', + 'sources': [ + '<@(common_sources)', + ], + }, + ], +} diff --git a/native_client_sdk/src/build_tools/debug_server/port/std_types.h b/native_client_sdk/src/build_tools/debug_server/port/std_types.h new file mode 100644 index 0000000..69b1d6d --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/port/std_types.h @@ -0,0 +1,44 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + +#ifndef NATIVE_CLIENT_PORT_STD_TYPES_H_ +#define NATIVE_CLIENT_PORT_STD_TYPES_H_ + +#ifdef WIN32 + +// Disable warning for Windows "safe" vsprintf_s, strcpy_s, etc... +// since we use the same version for Linux/Mac. +#pragma warning(disable:4996) + +// For intptr_t +#include <crtdefs.h> + +typedef signed char int8; +typedef short int16; +typedef int int32; +typedef long long int64; + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +typedef unsigned long long uint64; + +typedef signed char int8_t; +typedef short int16_t; +typedef int int32_t; +typedef long long int64_t; + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +#else +#include <stdint.h> +#endif + +#endif // NATIVE_CLIENT_PORT_STD_TYPES_H_ + diff --git a/native_client_sdk/src/build_tools/debug_server/port/thread.h b/native_client_sdk/src/build_tools/debug_server/port/thread.h new file mode 100644 index 0000000..c22637e --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/port/thread.h @@ -0,0 +1,64 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + + +// This module defines the interface for interacting with platform specific +// threads . This API provides a mechanism to query for a thread, by using +// the acquire method with the ID of a pre-existing thread. The register +// accessors are expected to return false if the thread is not in a state +// where the registers can be accessed, such RUNNING or SYSCALL. This API +// will throw: +// std::exception - if a unexpected OS Error is encountered. +// std::out_of_range - if the register index is out of range. + +#ifndef NATIVE_CLIENT_PORT_THREAD_H_ +#define NATIVE_CLIENT_PORT_THREAD_H_ 1 + +#include <stdlib.h> +#include <map> + +#include "native_client/src/debug_server/port/std_types.h" + +namespace port { + +class IThread { + public: + enum State { + DEAD =-1, // The thread has exited or been killed + RUNNING = 0, // The thread is currently running + SUSPENDED= 1, // The thread has been suspended + SIGNALED = 2, // The thread is signaled + SYSCALL = 3 // In a sys call, it's registers can not be modified. + }; + + typedef void (*CatchFunc_t)(uint32_t id, int8_t sig, void *cookie); + typedef std::map<uint32_t, IThread*> ThreadMap_t; + + virtual uint32_t GetId() = 0; + virtual State GetState() = 0; + + virtual bool SetStep(bool on) = 0; + + virtual bool GetRegister(uint32_t index, void *dst, uint32_t len) = 0; + virtual bool SetRegister(uint32_t index, void *src, uint32_t len) = 0; + + virtual bool Suspend() = 0; + virtual bool Resume() = 0; + + virtual void *GetContext() = 0; + + static IThread *Acquire(uint32_t id, bool create = true); + static void Release(IThread *thread); + static void SetExceptionCatch(CatchFunc_t func, void *cookie); + + protected: + virtual ~IThread() {} // Prevent delete of base pointer +}; + +} // namespace port + +#endif // PORT_THREAD_H_ + diff --git a/native_client_sdk/src/build_tools/debug_server/port/transport.h b/native_client_sdk/src/build_tools/debug_server/port/transport.h new file mode 100644 index 0000000..e42a9b4 --- /dev/null +++ b/native_client_sdk/src/build_tools/debug_server/port/transport.h @@ -0,0 +1,47 @@ +/* + * Copyright 2010 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can + * be found in the LICENSE file. + */ + + +// This module provides interfaces for an IO stream. The stream is +// expected to throw a std::exception if the stream is terminated on +// either side. +#ifndef NATIVE_CLIENT_PORT_TRANSPORT_H_ +#define NATIVE_CLIENT_PORT_TRANSPORT_H_ + +#include "native_client/src/debug_server/port/std_types.h" + +namespace port { + +class ITransport { + public: + // Read from this transport, return a negative value if there is an error + // otherwise return the number of bytes actually read. + virtual int32_t Read(void *ptr, int32_t len) = 0; + + // Write to this transport, return a negative value if there is an error + // otherwise return the number of bytes actually written. + virtual int32_t Write(const void *ptr, int32_t len) = 0; + + // Return true once read will not block or false after ms milliseconds. + virtual bool ReadWaitWithTimeout(uint32_t ms) = 0; + + // Disconnect the transport, R/W and Select will now throw an exception + virtual void Disconnect() = 0; + + // Attempt to connect to, or accept connection at the specified address + static ITransport *Connect(const char *addr); + static ITransport *Accept(const char *addr); + static void Free(ITransport *transport); + + protected: + virtual ~ITransport() {} // Prevent delete of base pointer +}; + + +} // namespace port + +#endif // NATIVE_CLIENT_PORT_TRANSPORT_H_ + diff --git a/native_client_sdk/src/build_tools/generate_installers.py b/native_client_sdk/src/build_tools/generate_installers.py new file mode 100755 index 0000000..348324c --- /dev/null +++ b/native_client_sdk/src/build_tools/generate_installers.py @@ -0,0 +1,160 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Assemble the final installer for each platform. + +At this time this is just a tarball. +""" + +import build_utils +import installer_contents +import optparse +import os +import re +import shutil +import stat +import string +import subprocess +import sys + +EXCLUDE_DIRS = ['.download', + '.svn'] + +INSTALLER_NAME = 'nacl-sdk.tgz' + + +# A list of all platforms that should use the Windows-based build strategy +# (which makes a self-extracting zip instead of a tarball). +WINDOWS_BUILD_PLATFORMS = ['cygwin', 'win32'] + + +# Return True if |file| should be excluded from the tarball. +def ExcludeFile(dir, file): + return (file.startswith('.DS_Store') or + file.startswith('._') or file == "make.cmd" or + file == 'DEPS' or file == 'codereview.settings' or + (file == "httpd.cmd")) + + +def main(argv): + bot = build_utils.BotAnnotator() + bot.Print('generate_installers is starting.') + + # Cache the current location so we can return here before removing the + # temporary dirs. + script_dir = os.path.abspath(os.path.dirname(__file__)) + home_dir = os.path.realpath(os.path.dirname(os.path.dirname(script_dir))) + + version_dir = build_utils.VersionString() + parent_dir = os.path.dirname(script_dir) + deps_file = os.path.join(parent_dir, 'DEPS') + + # Create a temporary directory using the version string, then move the + # contents of src to that directory, clean the directory of unwanted + # stuff and finally tar it all up using the platform's tar. There seems to + # be a problem with python's tarfile module and symlinks. + temp_dir = os.path.join(script_dir, 'installers_temp') + installer_dir = os.path.join(temp_dir, version_dir) + bot.Print('generate_installers chose installer directory: %s' % + (installer_dir)) + try: + os.makedirs(installer_dir, mode=0777) + except OSError: + pass + + # Decide environment to run in per platform. + env = os.environ.copy() + # Set up the required env variables for the scons builders. + env['NACL_SDK_ROOT'] = parent_dir + env['NACL_TARGET_PLATFORM'] = '.' # Use the repo's toolchain. + + # Build the experimental projects. + bot.BuildStep('build experimental') + bot.Print('generate_installers is building the experimental projects.') + experimental_path = os.path.join(home_dir, 'src', 'experimental') + scons_path = os.path.join(experimental_path, 'scons') + scons_cmd = scons_path + ' --nacl-platform="."' + subprocess.check_call(scons_cmd, + cwd=experimental_path, + env=env, + shell=True) + + # Use native tar to copy the SDK into the build location + # because copytree has proven to be error prone and is not supported on mac. + # We use a buffer for speed here. -1 causes the default OS size to be used. + bot.BuildStep('copy to install dir') + bot.Print('generate_installers is copying contents to install directory.') + tar_src_dir = os.path.realpath(os.curdir) + all_contents = (installer_contents.INSTALLER_CONTENTS + + installer_contents.DOCUMENTATION_FILES) + if sys.platform == 'darwin': + all_contents += installer_contents.MAC_ONLY_CONTENTS + else: + all_contents += installer_contents.LINUX_ONLY_CONTENTS + + all_contents_string = string.join( + installer_contents.GetFilesFromPathList(all_contents) + + installer_contents.GetDirectoriesFromPathList(all_contents), + ' ') + tar_cf = subprocess.Popen('tar cf - %s' % all_contents_string, + bufsize=-1, + cwd=tar_src_dir, env=env, shell=True, + stdout=subprocess.PIPE) + tar_xf = subprocess.Popen('tar xfv -', + cwd=installer_dir, env=env, shell=True, + stdin=tar_cf.stdout) + assert tar_xf.wait() == 0 + assert tar_cf.poll() == 0 + + # This loop prunes the result of os.walk() at each excluded dir, so that it + # doesn't descend into the excluded dir. + bot.Print('generate_installers is pruning installer directory') + for root, dirs, files in os.walk(installer_dir): + rm_dirs = [] + for excl in EXCLUDE_DIRS: + if excl in dirs: + dirs.remove(excl) + rm_dirs.append(os.path.join(root, excl)) + for rm_dir in rm_dirs: + shutil.rmtree(rm_dir) + rm_files = [os.path.join(root, f) for f in files if ExcludeFile(root, f)] + for rm_file in rm_files: + os.remove(rm_file) + + # Update the README file with date and version number + build_utils.UpdateReadMe(os.path.join(installer_dir, 'README')) + + bot.BuildStep('create archive') + bot.Print('generate_installers is creating the installer archive') + # Now that the SDK directory is copied and cleaned out, tar it all up using + # the native platform tar. + + # Set the default shell command and output name. Create a compressed tar + # archive from the contents of |input|. The contents of the tar archive + # have to be relative to |input| without including |input| in the path. + # Then copy the resulting archive to the |output| directory and make it + # read-only to group and others. + ar_cmd = ('tar -cvzf %(INSTALLER_NAME)s -C %(input)s . && ' + 'cp %(INSTALLER_NAME)s %(output)s && chmod 644 %(output)s') + + archive = os.path.join(home_dir, INSTALLER_NAME) + subprocess.check_call( + ar_cmd % ( + {'INSTALLER_NAME':INSTALLER_NAME, + 'input':installer_dir, + 'output':archive}), + cwd=temp_dir, + env=env, + shell=True) + + # Clean up. + shutil.rmtree(temp_dir) + return 0 + + +if __name__ == '__main__': + print "Directly running generate_installers.py is no longer supported." + print "Please instead run './scons installer' from the src directory." + sys.exit(1) diff --git a/native_client_sdk/src/build_tools/generate_windows_installer.py b/native_client_sdk/src/build_tools/generate_windows_installer.py new file mode 100755 index 0000000..5111e37 --- /dev/null +++ b/native_client_sdk/src/build_tools/generate_windows_installer.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Assemble the final installer for windows.""" + +import build_utils +import installer_contents +import make_nsis_installer +import optparse +import os +import shutil +import stat +import string +import subprocess +import sys +import tar_archive + +IGNORE_PATTERN = ('.download*', '.svn*') + +def main(argv): + bot = build_utils.BotAnnotator() + bot.Print('generate_windows_installer is starting.') + + # Make sure that we are running python version 2.6 or higher + (major, minor) = sys.version_info[:2] + assert major == 2 and minor >= 6 + # Cache the current location so we can return here before removing the + # temporary dirs. + script_dir = os.path.abspath(os.path.dirname(__file__)) + home_dir = os.path.realpath(os.path.dirname(os.path.dirname(script_dir))) + + version_dir = build_utils.VersionString() + parent_dir = os.path.dirname(script_dir) + deps_file = os.path.join(parent_dir, 'DEPS') + + # Create a temporary directory using the version string, then move the + # contents of src to that directory, clean the directory of unwanted + # stuff and finally create an installer. + temp_dir = os.path.join(script_dir, 'installers_temp') + installer_dir = os.path.join(temp_dir, version_dir) + bot.Print('generate_windows_installer chose installer directory: %s' % + (installer_dir)) + try: + os.makedirs(installer_dir, mode=0777) + except OSError: + pass + + env = os.environ.copy() + # Set up the required env variables for the scons builders. + env['NACL_SDK_ROOT'] = parent_dir + env['NACL_TARGET_PLATFORM'] = '.' # Use the repo's toolchain. + + # Build the experimental projects. + bot.BuildStep('build experimental') + bot.Print('generate_windows_installer is building the experimental projects.') + experimental_path = os.path.join(home_dir, 'src', 'experimental') + scons_path = os.path.join(experimental_path, 'scons.bat') + scons_cmd = scons_path + ' --nacl-platform="."' + subprocess.check_call(scons_cmd, cwd=experimental_path, env=env) + + # On windows we use copytree to copy the SDK into the build location + # because there is no native tar and using cygwin's version has proven + # to be error prone. + + # In case previous run didn't succeed, clean this out so copytree can make + # its target directories. + bot.BuildStep('copy to install dir') + bot.Print('generate_windows_installer is cleaning out install directory.') + shutil.rmtree(installer_dir) + bot.Print('generate_windows_installer: copying files to install directory.') + all_contents = installer_contents.INSTALLER_CONTENTS + \ + installer_contents.WINDOWS_ONLY_CONTENTS + for copy_source_dir in installer_contents.GetDirectoriesFromPathList( + all_contents): + copy_target_dir = os.path.join(installer_dir, copy_source_dir) + bot.Print("Copying %s to %s" % (copy_source_dir, copy_target_dir)) + shutil.copytree(copy_source_dir, + copy_target_dir, + symlinks=True, + ignore=shutil.ignore_patterns(*IGNORE_PATTERN)) + for copy_source_file in installer_contents.GetFilesFromPathList( + all_contents): + copy_target_file = os.path.join(installer_dir, copy_source_file) + bot.Print("Copying %s to %s" % (copy_source_file, copy_target_file)) + if not os.path.exists(os.path.dirname(copy_target_file)): + os.makedirs(os.path.dirname(copy_target_file)) + shutil.copy(copy_source_file, copy_target_file) + + # Do special processing on the user-readable documentation files. + for copy_source_file in installer_contents.DOCUMENTATION_FILES: + copy_target_file = os.path.join(installer_dir, copy_source_file + '.txt') + bot.Print("Copying %s to %s" % (copy_source_file, copy_target_file)) + with open(copy_source_file, "U") as source_file: + text = source_file.read().replace("\n", "\r\n") + with open(copy_target_file, "wb") as dest_file: + dest_file.write(text) + + # Update the README.txt file with date and version number + build_utils.UpdateReadMe(os.path.join(installer_dir, 'README.txt')) + + # Clean out the cruft. + bot.Print('generate_windows_installer: cleaning up installer directory.') + + # Make everything read/write (windows needs this). + for root, dirs, files in os.walk(installer_dir): + def UpdatePermissions(list): + for file in map(lambda f: os.path.join(root, f), list): + os.chmod(file, os.lstat(file).st_mode | stat.S_IWRITE | stat.S_IREAD) + UpdatePermissions(dirs) + UpdatePermissions(files) + + # Grab the toolchain manifest files and massage them so their paths are + # corrected for the actual toolchain layout in the SDK. The newlib toolchain + # manifest has these changes made: + # 1. Transform path components 'sdk/nacl-sdk' to 'toolchain/win_x86_newlib' + # 2. Remove the spurious 'sdk' directory entry. + # The glibc manifests can be used unmolested. + bot.BuildStep('generate toolchain manifests') + def TransformNewlibPath(npath): + return os.path.normpath(npath.replace('sdk/nacl-sdk', + 'toolchain/win_x86_newlib')) + newlib_manifest = tar_archive.TarArchive() + newlib_manifest.path_filter = TransformNewlibPath + newlib_manifest_path = os.path.join( + home_dir, + 'src', + installer_contents.GetToolchainManifest('newlib')) + newlib_manifest.InitWithManifest(newlib_manifest_path) + newlib_manifest.dirs.discard('sdk') + + glibc_manifest_path = os.path.join( + home_dir, + 'src', + installer_contents.GetToolchainManifest('glibc')) + glibc_manifest = tar_archive.TarArchive() + glibc_manifest.InitWithManifest(glibc_manifest_path) + + # Merge the newlib and glibc manifests and send them to the script generator. + bot.BuildStep('create Windows installer') + bot.Print('generate_windows_installer is creating the windows installer.') + build_tools_dir = os.path.join(home_dir, 'src', 'build_tools') + make_nsis_installer.MakeNsisInstaller( + installer_dir, + cwd=build_tools_dir, + toolchain_manifests=newlib_manifest | glibc_manifest) + bot.Print("Installer created!") + + # Clean up. + shutil.rmtree(temp_dir) + return 0 + + +if __name__ == '__main__': + print "Directly running generate_windows_installer.py is no longer supported." + print "Please instead run 'scons.bat installer' from the src directory." + sys.exit(1) diff --git a/native_client_sdk/src/build_tools/html_checker.py b/native_client_sdk/src/build_tools/html_checker.py new file mode 100755 index 0000000..b00ffad --- /dev/null +++ b/native_client_sdk/src/build_tools/html_checker.py @@ -0,0 +1,96 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +'''This file provides a simple HTML checker, based on the standard HTMLParser +library.''' + +import HTMLParser +import os +import sys + + +class HTMLChecker(HTMLParser.HTMLParser): + '''A simple html parser that can find attribute tags and validate syntax''' + + def __init__(self): + self.tag_list = [] + self.links = set() + HTMLParser.HTMLParser.__init__(self) + + def handle_starttag(self, tag, attrs): + attributes = dict(attrs) + # Skip tags if they're within a <script> tag so that we don't get confused. + if not self.tag_list or self.tag_list[-1] != 'script': + self.tag_list.append(tag) + if tag == 'a' and attributes.get('href'): + self.links.add(attributes['href']) + + def handle_endtag(self, tag): + try: + matching_tag = self.tag_list.pop() + except IndexError: + raise Exception('Unmatched tag %s at %s' % (tag, self.getpos())) + if matching_tag != tag: + if matching_tag == 'script': + self.tag_list.append(matching_tag) + else: + raise Exception('Wrong tag: Expected %s but got %s at %s' % + (matching_tag, tag, self.getpos())) + + def close(self): + if self.tag_list: + raise Exception('Reached end-of-file with unclosed tags: %s' + % self.tag_list) + HTMLParser.HTMLParser.close(self) + + +def ValidateFile(filename): + '''Run simple html syntax checks on given file to validate tags and links + + Args: + filename: Name of file to validate + + Returns: + tuple containing: + set of urls from this file + set of absolute paths from this file''' + (directory, basename) = os.path.split(os.path.abspath(filename)) + parser = HTMLChecker() + with open(filename, 'r') as file: + parser.feed(file.read()) + parser.close() + files = set() + urls = set() + for link in parser.links: + if link.startswith('http://') or link.startswith('https://'): + urls.add(link) + else: + files.add(os.path.abspath(os.path.join(directory, link))) + return urls, files + + +def ValidateAllLinks(filenames): + '''Validate all the links in filename and all linked files on this domain''' + validated_files = set() + validated_urls = set() + need_to_validate = set([os.path.abspath(file) for file in filenames]) + while need_to_validate: + file = need_to_validate.pop() + print 'Evaluating %s' % file + urls, files = ValidateFile(file) + validated_files.add(file) + need_to_validate |= files - validated_files + + +def main(argv): + '''Run ValidateFile on each argument + + Args: + argv: Command-line arguments''' + ValidateAllLinks(argv) + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:]))
\ No newline at end of file diff --git a/native_client_sdk/src/build_tools/install_boost/install_boost.py b/native_client_sdk/src/build_tools/install_boost/install_boost.py new file mode 100755 index 0000000..ee93e58 --- /dev/null +++ b/native_client_sdk/src/build_tools/install_boost/install_boost.py @@ -0,0 +1,110 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Install boost headers into a list of toolchains, in such a way that it gets +pulled into the SDK installer. Note that this script only installs boost +headers, and does not build any of the boost libraries that require building. +""" + +import build_utils +import os +import shutil +import sys +import tarfile +import tempfile +import urllib + +from optparse import OptionParser + +# The original boost distro can be found here: +# http://sourceforge.net/projects/boost/files/boost/1.47.0/\ +# boost_1_47_0.tar.gz/download +BOOST_URL = ('http://commondatastorage.googleapis.com/nativeclient-mirror' + '/nacl/boost_1_47_0.tar.gz') +BOOST_PATH = 'boost_1_47_0' + + +def DownloadAndExtract(working_dir, url, path): + boost_path = os.path.abspath(os.path.join(working_dir, path)) + print 'Download: %s' % url + try: + (tgz_file, headers) = urllib.urlretrieve(url, '%s.tgz' % boost_path) + tar = None + try: + tar = tarfile.open(tgz_file) + tar.extractall(working_dir) + finally: + if tar: + tar.close() + except (URLError, ContentTooShortError): + print 'Error retrieving %s' % url + raise + + +# Install the boost headers into the toolchains. +def InstallBoost(options): + # Create a temporary working directory. This is where all the tar files go + # and where the packages get built prior to installation in the toolchain. + working_dir = tempfile.mkdtemp(prefix='boost') + try: + DownloadAndExtract(working_dir, BOOST_URL, BOOST_PATH) + except: + print "Error in download" + return 1 + boost_include = options.third_party_dir + build_utils.ForceMakeDirs(boost_include) + boost_path = os.path.abspath(os.path.join(working_dir, BOOST_PATH)) + # Copy the headers. + print 'Installing boost headers into %s...' % boost_include + dst_include_dir = os.path.join(boost_include, 'boost') + shutil.rmtree(dst_include_dir, ignore_errors=True) + shutil.copytree(os.path.join(boost_path, 'boost'), + dst_include_dir, + symlinks=True) + # Copy the license file. + print 'Installing boost license...' + shutil.copy(os.path.join(boost_path, 'LICENSE_1_0.txt'), dst_include_dir) + + # Clean up. + shutil.rmtree(working_dir, ignore_errors=True) + return 0 + + +# Parse the command-line args and set up the options object. There is one +# command-line switch: +# --toolchain=<path to the platform-specific toolchain> +# e.g.: --toolchain=../toolchain/mac-x86 +# default is 'toolchain'. +# --toolchain can appear more than once, the Boost library is +# installed into each toolchain listed. +def main(argv): + parser = OptionParser() + parser.add_option( + '-t', '--toolchain', dest='toolchains', + action='append', + type='string', + help='NaCl toolchain directory') + parser.add_option( + '--third-party', dest='third_party_dir', + type='string', + default='third_party', + help='location of third_party directory') + (options, args) = parser.parse_args(argv) + if args: + print 'WARNING: unrecognized argument: %s' % str(args) + parser.print_help() + + if not options.toolchains: + options.toolchains = [build_utils.TOOLCHAIN_AUTODETECT] + options.toolchains = [build_utils.NormalizeToolchain(tc) + for tc in options.toolchains] + + print "Installing boost into %s" % str(options.third_party_dir) + return InstallBoost(options) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/native_client_sdk/src/build_tools/install_gtest/install_gtest.py b/native_client_sdk/src/build_tools/install_gtest/install_gtest.py new file mode 100644 index 0000000..7c489b0 --- /dev/null +++ b/native_client_sdk/src/build_tools/install_gtest/install_gtest.py @@ -0,0 +1,239 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Install GTest and GMock that can be linked to a NaCl module. By default +this script builds both the 32- and 64-bit versions of the libraries, and +installs them in <toolchain>/nacl/usr/lib and <toolchain>/nacl64/usr/lib. The +header files are also installed in <toolchain>/<ncal_spec>/usr/include. +""" + +import build_utils +import os +import shutil +import subprocess +import sys +import tempfile +import urllib + +from optparse import OptionParser + +# Default values for the --toolchain and --bit-spec command line arguments. +BIT_SPEC_DEFAULT = "32,64" + +# The original gtest distro can be found here: +# http://code.google.com/p/googletest/downloads/detail?name=gtest-1.5.0.tar.gz +GTEST_URL = ("http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/" + "gtest-1.5.0.tgz") +GTEST_PATH = "gtest-1.5.0" +GTEST_PATCH_FILE = "nacl-gtest-1.5.0.patch" + +# The original gmock distro can be found here: +# http://googlemock.googlecode.com/files/gmock-1.5.0.tar.gz +GMOCK_URL = ("http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/" + "gmock-1.5.0.tgz") +GMOCK_PATH = "gmock-1.5.0" +GMOCK_PATCH_FILE = "nacl-gmock-1.5.0.patch" + + +# Create a temporary working directory. This is where all the tar files go +# and where the packages get built prior to installation in the toolchain. +def MakeWorkingDir(options): + # Pick work directory. + if not options.working_dir: + options.working_dir = tempfile.mkdtemp(prefix='gtest') + options.must_clean_up = True + # Go into working area. + options.old_cwd = os.getcwd() + os.chdir(options.working_dir) + + +# Download GTest and GMock into the working directory, then extract them. +def DownloadAndExtractAll(options): + def DownloadAndExtract(url, path): + print "Download: %s" % url + (zip_file, headers) = urllib.urlretrieve(url, '%s.tgz' % path) + p = subprocess.Popen('tar xzf %s' % (zip_file), + env=options.shell_env, + shell=True) + assert p.wait() == 0 + + os.chdir(options.working_dir) + try: + DownloadAndExtract(GTEST_URL, GTEST_PATH) + DownloadAndExtract(GMOCK_URL, GMOCK_PATH) + except (URLError, ContentTooShortError): + os.chdir(options.old_cwd) + print "Error retrieving %s" % url + raise + + os.chdir(options.old_cwd) + + +# Apply the patch files to the extracted GTest and GMock pacakges. +def PatchAll(options): + def Patch(abs_path, patch_file): + print "Patching %s with: %s" % (abs_path, patch_file) + p = subprocess.Popen('chmod -R a+w . && patch -p0 < %s' % (patch_file), + cwd=abs_path, + env=options.shell_env, + shell=True) + assert p.wait() == 0 + + Patch(options.working_dir, os.path.join(options.script_dir, GTEST_PATCH_FILE)) + Patch(options.working_dir, os.path.join(options.script_dir, GMOCK_PATCH_FILE)) + + +# Build GTest and GMock, then install them into the toolchain. Note that +# GTest has to be built and installed into the toolchain before GMock can be +# built, because GMock relies on headers from GTest. This method sets up all +# the necessary shell environment variables for the makefiles, such as CC and +# CXX. +def BuildAndInstallAll(options): + def BuildInPath(abs_path, shell_env): + # Run make clean and make in |abs_path|. Assumes there is a makefile in + # |abs_path|, if not then the assert will trigger. + print "Building in %s" % (abs_path) + p = subprocess.Popen('make clean && make -j4', + cwd=abs_path, + env=shell_env, + shell=True) + assert p.wait() == 0 + + def InstallLib(lib, src_path, dst_path, shell_env): + # Use the install untility to install |lib| from |src_path| into + # |dst_path|. + p = subprocess.Popen("install -m 644 %s %s" % (lib, dst_path), + cwd=src_path, + env=shell_env, + shell=True) + assert p.wait() == 0 + + # Build and install for each bit-spec. + for bit_spec in options.bit_spec.split(','): + if bit_spec == '64': + nacl_spec = 'x86_64-nacl' + else: + nacl_spec = 'i686-nacl' + + print 'Building gtest and gmock for NaCl spec: %s.' % nacl_spec + # Make sure the target directories exist. + nacl_usr_include = os.path.join(options.toolchain, + nacl_spec, + 'usr', + 'include') + nacl_usr_lib = os.path.join(options.toolchain, + nacl_spec, + 'usr', + 'lib') + build_utils.ForceMakeDirs(nacl_usr_include) + build_utils.ForceMakeDirs(nacl_usr_lib) + + # Set up the nacl-specific environment variables used by make. + build_env = options.shell_env.copy() + toolchain_bin = os.path.join(options.toolchain, 'bin') + build_env['CC'] = os.path.join(toolchain_bin, '%s-gcc' % nacl_spec) + build_env['CXX'] = os.path.join(toolchain_bin, '%s-g++' % nacl_spec) + build_env['AR'] = os.path.join(toolchain_bin, '%s-ar' % nacl_spec) + build_env['RANLIB'] = os.path.join(toolchain_bin, '%s-ranlib' % nacl_spec) + build_env['LD'] = os.path.join(toolchain_bin, '%s-ld' % nacl_spec) + build_env['NACL_TOOLCHAIN_ROOT'] = options.toolchain + + # GTest has to be built & installed before GMock can be built. + gtest_path = os.path.join(options.working_dir, GTEST_PATH) + BuildInPath(gtest_path, build_env) + gtest_tar_excludes = "--exclude='gtest-death-test.h' \ + --exclude='gtest-death-test-internal.h'" + tar_cf = subprocess.Popen("tar cf - %s gtest" % gtest_tar_excludes, + cwd=os.path.join(gtest_path, 'include'), + env=build_env, + shell=True, + stdout=subprocess.PIPE) + tar_xf = subprocess.Popen("tar xfp -", + cwd=nacl_usr_include, + env=build_env, + shell=True, + stdin=tar_cf.stdout) + tar_copy_err = tar_xf.communicate()[1] + InstallLib('libgtest.a', gtest_path, nacl_usr_lib, build_env) + + gmock_path = os.path.join(options.working_dir, GMOCK_PATH) + BuildInPath(gmock_path, build_env) + tar_cf = subprocess.Popen("tar cf - gmock", + cwd=os.path.join(gmock_path, 'include'), + env=options.shell_env, + shell=True, + stdout=subprocess.PIPE) + tar_xf = subprocess.Popen("tar xfp -", + cwd=nacl_usr_include, + env=options.shell_env, + shell=True, + stdin=tar_cf.stdout) + tar_copy_err = tar_xf.communicate()[1] + InstallLib('libgmock.a', gmock_path, nacl_usr_lib, build_env) + + +# Main driver method that creates a working directory, then downloads and +# extracts GTest and GMock, patches and builds them both, then installs them +# into the toolchain specified in |options.toolchain|. +def InstallTestingLibs(options): + MakeWorkingDir(options) + try: + DownloadAndExtractAll(options) + PatchAll(options) + except: + return 1 + + BuildAndInstallAll(options) + # Clean up. + if options.must_clean_up: + shutil.rmtree(options.working_dir, ignore_errors=True) + return 0 + + +# Parse the command-line args and set up the options object. There are two +# command-line switches: +# --toolchain=<path to the platform-specific toolchain> +# e.g.: --toolchain=../toolchain/mac-x86 +# default is |TOOLCHAIN_AUTODETECT| which means try to determine +# the toolchain dir from |sys.platform|. +# --bit-spec=<comma-separated list of instruction set bit-widths +# e.g.: --bit_spec=32,64 +# default is "32,64" which means build 32- and 64-bit versions +# of the libraries. +def main(argv): + shell_env = os.environ; + if not build_utils.CheckPatchVersion(shell_env): + sys.exit(0) + + parser = OptionParser() + parser.add_option( + '-t', '--toolchain', dest='toolchain', + default=build_utils.TOOLCHAIN_AUTODETECT, + help='where to put the testing libraries') + parser.add_option( + '-b', '--bit-spec', dest='bit_spec', + default=BIT_SPEC_DEFAULT, + help='comma separated list of instruction set bit-widths') + parser.add_option( + '-w', '--working_dir', dest='working_dir', + default=None, + help='where to untar and build the test libs.') + (options, args) = parser.parse_args(argv) + if args: + print 'ERROR: invalid argument: %s' % str(args) + parser.print_help() + sys.exit(1) + + options.must_clean_up = False + options.shell_env = shell_env + options.script_dir = os.path.abspath(os.path.dirname(__file__)) + options.toolchain = build_utils.NormalizeToolchain(options.toolchain) + print "Installing testing libs into toolchain %s" % options.toolchain + + return InstallTestingLibs(options) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/native_client_sdk/src/build_tools/install_gtest/nacl-gmock-1.5.0.patch b/native_client_sdk/src/build_tools/install_gtest/nacl-gmock-1.5.0.patch new file mode 100644 index 0000000..0f22bb1 --- /dev/null +++ b/native_client_sdk/src/build_tools/install_gtest/nacl-gmock-1.5.0.patch @@ -0,0 +1,29 @@ +diff -Naur gmock-1.5.0/Makefile gmock-1.5.0.nacl/Makefile +--- gmock-1.5.0/Makefile 1969-12-31 17:00:00.000000000 -0700 ++++ gmock-1.5.0.nacl/Makefile 2010-07-07 14:28:27.000000000 -0600 +@@ -0,0 +1,25 @@ ++CXXFLAGS = -O0 -g ++INCLUDE = -Iinclude \ ++ -I. \ ++ -I$(NACL_TOOLCHAIN_ROOT)/nacl/usr/include \ ++ -I$(NACL_TOOLCHAIN_ROOT)/nacl64/usr/include ++LIB_GMOCK = libgmock.a ++OBJ_DIR = obj ++ ++OBJ_FILES = gmock-all.o \ ++ gmock_main.o ++ ++all: $(LIB_GMOCK) ++ @echo "Making libgmock" ++ ++clean: ++ rm -rf $(OBJ_DIR) $(LIB_GMOCK) ++ ++$(OBJ_DIR): ++ mkdir $(OBJ_DIR) ++ ++$(OBJ_FILES): %.o: src/%.cc $(OBJ_DIR) ++ $(CXX) -c $(INCLUDE) $(CXXFLAGS) $< -o $(OBJ_DIR)/$@ ++ ++$(LIB_GMOCK): $(OBJ_DIR) $(OBJ_FILES) ++ cd $(OBJ_DIR) && $(AR) rcs ../$(LIB_GMOCK) $(OBJ_FILES) diff --git a/native_client_sdk/src/build_tools/install_gtest/nacl-gtest-1.5.0.patch b/native_client_sdk/src/build_tools/install_gtest/nacl-gtest-1.5.0.patch new file mode 100644 index 0000000..389c51f --- /dev/null +++ b/native_client_sdk/src/build_tools/install_gtest/nacl-gtest-1.5.0.patch @@ -0,0 +1,226 @@ +diff -Naur gtest-1.5.0/Makefile gtest-1.5.0.nacl/Makefile +--- gtest-1.5.0/Makefile 1969-12-31 17:00:00.000000000 -0700 ++++ gtest-1.5.0.nacl/Makefile 2010-07-08 09:49:37.000000000 -0600 +@@ -0,0 +1,25 @@ ++CXXFLAGS = -O0 -g ++INCLUDE = -Iinclude -I. ++LIB_GTEST = libgtest.a ++OBJ_DIR = obj ++ ++OBJ_FILES = gtest.o \ ++ gtest-filepath.o \ ++ gtest-port.o \ ++ gtest-test-part.o \ ++ gtest-typed-test.o ++ ++all: $(LIB_GTEST) ++ @echo "Making libgtest" ++ ++clean: ++ rm -rf $(OBJ_DIR) $(LIB_GTEST) ++ ++$(OBJ_DIR): ++ mkdir $(OBJ_DIR) ++ ++$(OBJ_FILES): %.o: src/%.cc $(OBJ_DIR) ++ $(CXX) -c $(INCLUDE) $(CXXFLAGS) $< -o $(OBJ_DIR)/$@ ++ ++$(LIB_GTEST): $(OBJ_DIR) $(OBJ_FILES) ++ cd $(OBJ_DIR) && $(AR) rcs ../$(LIB_GTEST) $(OBJ_FILES) +diff -Naur gtest-1.5.0/include/gtest/gtest.h gtest-1.5.0.nacl/include/gtest/gtest.h +--- gtest-1.5.0/include/gtest/gtest.h 2010-04-15 16:02:03.000000000 -0600 ++++ gtest-1.5.0.nacl/include/gtest/gtest.h 2010-07-08 09:49:37.000000000 -0600 +@@ -56,7 +56,6 @@ + + #include <gtest/internal/gtest-internal.h> + #include <gtest/internal/gtest-string.h> +-#include <gtest/gtest-death-test.h> + #include <gtest/gtest-message.h> + #include <gtest/gtest-param-test.h> + #include <gtest/gtest_prod.h> +diff -Naur gtest-1.5.0/include/gtest/internal/gtest-port.h gtest-1.5.0.nacl/include/gtest/internal/gtest-port.h +--- gtest-1.5.0/include/gtest/internal/gtest-port.h 2010-04-15 16:02:02.000000000 -0600 ++++ gtest-1.5.0.nacl/include/gtest/internal/gtest-port.h 2010-07-08 09:49:37.000000000 -0600 +@@ -222,14 +222,13 @@ + #endif // __CYGWIN__ + + #if GTEST_OS_CYGWIN || GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_SYMBIAN || \ +- GTEST_OS_SOLARIS || GTEST_OS_AIX ++ GTEST_OS_SOLARIS || GTEST_OS_AIX && !defined(__native_client__) + + // On some platforms, <regex.h> needs someone to define size_t, and + // won't compile otherwise. We can #include it here as we already + // included <stdlib.h>, which is guaranteed to define size_t through + // <stddef.h>. + #include <regex.h> // NOLINT +-#include <strings.h> // NOLINT + #include <sys/types.h> // NOLINT + #include <time.h> // NOLINT + #include <unistd.h> // NOLINT +@@ -485,7 +484,9 @@ + #if (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS || \ + (GTEST_OS_WINDOWS_DESKTOP && _MSC_VER >= 1400) || \ + GTEST_OS_WINDOWS_MINGW || GTEST_OS_AIX) ++#if !defined(__native_client__) + #define GTEST_HAS_DEATH_TEST 1 ++#endif + #include <vector> // NOLINT + #endif + +@@ -839,11 +840,7 @@ + // testing Google Test's own constructs. Don't use it in user tests, + // either directly or indirectly. + inline void SleepMilliseconds(int n) { +- const timespec time = { +- 0, // 0 seconds. +- n * 1000L * 1000L, // And n ms. +- }; +- nanosleep(&time, NULL); ++ usleep(100 * n); + } + + // Allows a controller thread to pause execution of newly created +diff -Naur gtest-1.5.0/src/gtest-death-test.cc gtest-1.5.0.nacl/src/gtest-death-test.cc +--- gtest-1.5.0/src/gtest-death-test.cc 2010-04-15 16:02:04.000000000 -0600 ++++ gtest-1.5.0.nacl/src/gtest-death-test.cc 2010-07-08 09:49:38.000000000 -0600 +@@ -34,6 +34,8 @@ + #include <gtest/gtest-death-test.h> + #include <gtest/internal/gtest-port.h> + ++#undef GTEST_HAS_DEATH_TEST ++ + #if GTEST_HAS_DEATH_TEST + + #if GTEST_OS_MAC +diff -Naur gtest-1.5.0/src/gtest-filepath.cc gtest-1.5.0.nacl/src/gtest-filepath.cc +--- gtest-1.5.0/src/gtest-filepath.cc 2010-04-15 16:02:04.000000000 -0600 ++++ gtest-1.5.0.nacl/src/gtest-filepath.cc 2010-07-08 09:49:38.000000000 -0600 +@@ -96,21 +96,6 @@ + #endif + } + +-// Returns the current working directory, or "" if unsuccessful. +-FilePath FilePath::GetCurrentDir() { +-#if GTEST_OS_WINDOWS_MOBILE +- // Windows CE doesn't have a current directory, so we just return +- // something reasonable. +- return FilePath(kCurrentDirectoryString); +-#elif GTEST_OS_WINDOWS +- char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; +- return FilePath(_getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd); +-#else +- char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; +- return FilePath(getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd); +-#endif // GTEST_OS_WINDOWS_MOBILE +-} +- + // Returns a copy of the FilePath with the case-insensitive extension removed. + // Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns + // FilePath("dir/file"). If a case-insensitive extension is not +diff -Naur gtest-1.5.0/src/gtest-internal-inl.h gtest-1.5.0.nacl/src/gtest-internal-inl.h +--- gtest-1.5.0/src/gtest-internal-inl.h 2010-04-15 16:02:04.000000000 -0600 ++++ gtest-1.5.0.nacl/src/gtest-internal-inl.h 2010-07-08 09:49:39.000000000 -0600 +@@ -67,13 +67,6 @@ + + namespace testing { + +-// Declares the flags. +-// +-// We don't want the users to modify this flag in the code, but want +-// Google Test's own unit tests to be able to access it. Therefore we +-// declare it here as opposed to in gtest.h. +-GTEST_DECLARE_bool_(death_test_use_fork); +- + namespace internal { + + // The value of GetTestTypeId() as seen from within the Google Test +@@ -154,10 +147,7 @@ + break_on_failure_ = GTEST_FLAG(break_on_failure); + catch_exceptions_ = GTEST_FLAG(catch_exceptions); + color_ = GTEST_FLAG(color); +- death_test_style_ = GTEST_FLAG(death_test_style); +- death_test_use_fork_ = GTEST_FLAG(death_test_use_fork); + filter_ = GTEST_FLAG(filter); +- internal_run_death_test_ = GTEST_FLAG(internal_run_death_test); + list_tests_ = GTEST_FLAG(list_tests); + output_ = GTEST_FLAG(output); + print_time_ = GTEST_FLAG(print_time); +@@ -174,10 +164,7 @@ + GTEST_FLAG(break_on_failure) = break_on_failure_; + GTEST_FLAG(catch_exceptions) = catch_exceptions_; + GTEST_FLAG(color) = color_; +- GTEST_FLAG(death_test_style) = death_test_style_; +- GTEST_FLAG(death_test_use_fork) = death_test_use_fork_; + GTEST_FLAG(filter) = filter_; +- GTEST_FLAG(internal_run_death_test) = internal_run_death_test_; + GTEST_FLAG(list_tests) = list_tests_; + GTEST_FLAG(output) = output_; + GTEST_FLAG(print_time) = print_time_; +@@ -193,10 +180,7 @@ + bool break_on_failure_; + bool catch_exceptions_; + String color_; +- String death_test_style_; +- bool death_test_use_fork_; + String filter_; +- String internal_run_death_test_; + bool list_tests_; + String output_; + bool print_time_; +@@ -699,18 +683,6 @@ + void AddTestInfo(Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc, + TestInfo * test_info) { +- // In order to support thread-safe death tests, we need to +- // remember the original working directory when the test program +- // was first invoked. We cannot do this in RUN_ALL_TESTS(), as +- // the user may have changed the current directory before calling +- // RUN_ALL_TESTS(). Therefore we capture the current directory in +- // AddTestInfo(), which is called to register a TEST or TEST_F +- // before main() is reached. +- if (original_working_dir_.IsEmpty()) { +- original_working_dir_.Set(FilePath::GetCurrentDir()); +- GTEST_CHECK_(!original_working_dir_.IsEmpty()) +- << "Failed to get the current working directory."; +- } + + GetTestCase(test_info->test_case_name(), + test_info->test_case_comment(), +diff -Naur gtest-1.5.0/src/gtest.cc gtest-1.5.0.nacl/src/gtest.cc +--- gtest-1.5.0/src/gtest.cc 2010-04-15 16:02:04.000000000 -0600 ++++ gtest-1.5.0.nacl/src/gtest.cc 2010-07-08 09:49:40.000000000 -0600 +@@ -57,7 +57,9 @@ + #include <limits.h> + #include <sched.h> + // Declares vsnprintf(). This header is not available on Windows. ++#if !defined(__native_client__) + #include <strings.h> ++#endif + #include <sys/mman.h> + #include <sys/time.h> + #include <unistd.h> +@@ -4597,13 +4599,7 @@ + ParseBoolFlag(arg, kCatchExceptionsFlag, + >EST_FLAG(catch_exceptions)) || + ParseStringFlag(arg, kColorFlag, >EST_FLAG(color)) || +- ParseStringFlag(arg, kDeathTestStyleFlag, +- >EST_FLAG(death_test_style)) || +- ParseBoolFlag(arg, kDeathTestUseFork, +- >EST_FLAG(death_test_use_fork)) || + ParseStringFlag(arg, kFilterFlag, >EST_FLAG(filter)) || +- ParseStringFlag(arg, kInternalRunDeathTestFlag, +- >EST_FLAG(internal_run_death_test)) || + ParseBoolFlag(arg, kListTestsFlag, >EST_FLAG(list_tests)) || + ParseStringFlag(arg, kOutputFlag, >EST_FLAG(output)) || + ParseBoolFlag(arg, kPrintTimeFlag, >EST_FLAG(print_time)) || +diff -Naur gtest-1.5.0/src/gtest_main.cc gtest-1.5.0.nacl/src/gtest_main.cc +--- gtest-1.5.0/src/gtest_main.cc 2010-04-15 16:02:04.000000000 -0600 ++++ gtest-1.5.0.nacl/src/gtest_main.cc 2010-07-08 10:14:13.000000000 -0600 +@@ -33,7 +33,6 @@ + + int main(int argc, char **argv) { + std::cout << "Running main() from gtest_main.cc\n"; +- + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); + } diff --git a/native_client_sdk/src/build_tools/install_nsis.py b/native_client_sdk/src/build_tools/install_nsis.py new file mode 100644 index 0000000..9400347 --- /dev/null +++ b/native_client_sdk/src/build_tools/install_nsis.py @@ -0,0 +1,155 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Install the NSIS compiler and its SDK.""" + +import os +import shutil +import subprocess +import zipfile + + +# The name of the archive that contains the AccessControl extensions. +ACCESS_CONTROL_ZIP = 'AccessControl.zip' +# The AccessControl plugin. The installer check for this before installing. +ACCESS_CONTROL_DLL = 'AccessControl.dll' +# The name of the MkLnk extension DLL. This is checked into the SDK repo. +MKLINK_DLL = os.path.join('MkLink', 'Release Unicode', 'MkLink.dll') +# The NSIS compiler. The installer checks for this before installing. +NSIS_COMPILER = 'makensis.exe' +# The default directory name for the NSIS installation. +NSIS_DIR = 'NSIS' +# The name of the NSIS installer. This file is checked into the SDK repo. +NSIS_INSTALLER = 'nsis-2.46-Unicode-setup.exe' + +def MakeDirsIgnoreExist(dir_path, mode=0755): + '''Recursively make a directory path. + + Recursively makes all the paths in |dir_path|. If |dir_path| already exists, + do nothing. + + Args: + dir_path: A directory path. All necessary comonents of this path are + created. + mode: The mode to use if creating |dir_path|. Default is 0755. + ''' + if not os.path.exists(dir_path): + os.makedirs(dir_path, mode=mode) + + +def InstallNsis(installer_exe, target_dir, force=False): + '''Install NSIS into |target_dir|. + + Args: + installer_exe: The full path to the NSIS self-extracting installer. + + target_dir: The target directory for NSIS. The installer is run in this + directory and produces a directory named NSIS that contains the NSIS + compiler, etc. Must be defined. + + force: Whether or not to force an installation. + ''' + if not os.path.exists(installer_exe): + raise IOError('%s not found' % installer_exe) + + if not os.path.isabs(installer_exe): + raise ValueError('%s must be an absolute path' % installer_exe) + if not os.path.isabs(target_dir): + raise ValueError('%s must be an absolute path' % target_dir) + + if force or not os.path.exists(os.path.join(target_dir, NSIS_COMPILER)): + MakeDirsIgnoreExist(target_dir) + subprocess.check_call([installer_exe, + '/S', + '/D=%s' % target_dir], + cwd=os.path.dirname(installer_exe), + shell=True) + + +def InstallAccessControlExtensions(cwd, + access_control_zip, + target_dir, + force=False): + '''Install the AccessControl extensions into the NSIS directory. + + Args: + cwd: The current working directory. + + access_control_zip: The full path of the AccessControl.zip file. The + contents of this file are extracted using python's zipfile package. + + target_dir: The full path of the target directory for the AccessControl + extensions. + + force: Whether or not to force an installation. + ''' + if not os.path.exists(access_control_zip): + raise IOError('%s not found' % access_control_zip) + + dst_plugin_dir = os.path.join(target_dir, 'Plugins') + if force or not os.path.exists(os.path.join(dst_plugin_dir, + ACCESS_CONTROL_DLL)): + MakeDirsIgnoreExist(dst_plugin_dir) + zip_file = zipfile.ZipFile(access_control_zip, 'r') + try: + zip_file.extractall(target_dir) + finally: + zip_file.close() + # Move the AccessControl plugin DLLs into the main NSIS Plugins directory. + access_control_plugin_dir = os.path.join(target_dir, + 'AccessControl', + 'Plugins') + access_control_plugins = [os.path.join(access_control_plugin_dir, p) + for p in os.listdir(access_control_plugin_dir)] + for plugin in access_control_plugins: + shutil.copy(plugin, dst_plugin_dir) + + +def InstallMkLinkExtensions(mklink_dll, target_dir, force=False): + '''Install the AccessControl extensions into the NSIS directory. + + Args: + mklink_dll: The full path of the MkLink.dll file. + + target_dir: The full path of the target directory for the MkLink + extensions. + + force: Whether or not to force an installation. + ''' + if not os.path.exists(mklink_dll): + raise IOError('%s not found' % mklink_dll) + + dst_plugin_dir = os.path.join(target_dir, 'Plugins') + dst_mklink_dll = os.path.join(dst_plugin_dir, os.path.basename(mklink_dll)) + if force or not os.path.exists(dst_mklink_dll): + MakeDirsIgnoreExist(dst_plugin_dir) + # Copy the MkLink plugin DLLs into the main NSIS Plugins directory. + shutil.copy(mklink_dll, dst_mklink_dll) + + +def Install(cwd, target_dir=None, force=False): + '''Install the entire NSIS compiler and SDK with extensions. + + Installs the NSIS SDK and extensions into |target_dir|. By default, the + target directory is NSIS_DIR under |cwd|. + + If NSIS is already installed and |force| is False, do nothing. If |force| + is True or NSIS is not already installed, then install NSIS and the necessary + extensions. + + Args: + cwd: The current working directory. + + target_dir: NSIS is installed here. If |target_dir| is None, then NSIS is + installed in its default location, which is NSIS_DIR under |cwd|. + + force: True means install NSIS whether it already exists or not. + ''' + # If the NSIS compiler and SDK hasn't been installed, do so now. + nsis_dir = target_dir or os.path.join(cwd, NSIS_DIR) + InstallNsis(os.path.join(cwd, NSIS_INSTALLER), nsis_dir, force=force) + InstallMkLinkExtensions(os.path.join(cwd, MKLINK_DLL), nsis_dir, force=force) + InstallAccessControlExtensions( + cwd, os.path.join(cwd, ACCESS_CONTROL_ZIP), nsis_dir, force=force) diff --git a/native_client_sdk/src/build_tools/install_third_party.py b/native_client_sdk/src/build_tools/install_third_party.py new file mode 100755 index 0000000..431a684 --- /dev/null +++ b/native_client_sdk/src/build_tools/install_third_party.py @@ -0,0 +1,78 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Build and install all the third-party tools and libraries required to build +the SDK code. To add a script, add it to the array |THIRD_PARTY_SCRIPTS|. +Before running the scripts, a couple of environment variables get set: + PYTHONPATH - append this script's dir to the search path for module import. + NACL_SDK_ROOT - forced to point to the root of this repo. +""" + +import os +import subprocess +import sys + +from optparse import OptionParser + +# Append to PYTHONPATH in this very non-compliant way so that this script can be +# run from a DEPS hook, where the normal path rules don't apply. +SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) +THIRD_PARTY_DIR = os.path.join(os.path.dirname(SCRIPT_DIR), 'third_party') +SCONS_DIR = os.path.join(THIRD_PARTY_DIR, 'scons-2.0.1', 'engine') +sys.path.append(SCRIPT_DIR) +sys.path.append(SCONS_DIR) + +import build_utils + + +THIRD_PARTY_SCRIPTS = [ + os.path.join('install_boost', 'install_boost.py'), +] + + +def main(argv): + parser = OptionParser() + parser.add_option( + '-a', '--all-toolchains', dest='all_toolchains', + action='store_true', + help='Install into all available toolchains.') + (options, args) = parser.parse_args(argv) + if args: + print 'ERROR: invalid argument: %s' % str(args) + parser.print_help() + sys.exit(1) + + python_paths = [SCRIPT_DIR, SCONS_DIR] + shell_env = os.environ.copy() + python_paths += [shell_env.get('PYTHONPATH', '')] + shell_env['PYTHONPATH'] = os.pathsep.join(python_paths) + + # Force NACL_SDK_ROOT to point to the toolchain in this repo. + nacl_sdk_root = os.path.dirname(SCRIPT_DIR) + shell_env['NACL_SDK_ROOT'] = nacl_sdk_root + + script_argv = [arg for arg in argv if not arg in ['-a', '--all-toolchains']] + if options.all_toolchains: + script_argv += [ + '--toolchain=%s' % ( + build_utils.NormalizeToolchain(base_dir=nacl_sdk_root, + arch='x86', + variant='glibc')), + '--toolchain=%s' % ( + build_utils.NormalizeToolchain(base_dir=nacl_sdk_root, + arch='x86', + variant='newlib')), + '--third-party=%s' % THIRD_PARTY_DIR, + ] + + for script in THIRD_PARTY_SCRIPTS: + print "Running install script: %s" % os.path.join(SCRIPT_DIR, script) + py_command = [sys.executable, os.path.join(SCRIPT_DIR, script)] + subprocess.check_call(py_command + script_argv, env=shell_env) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/native_client_sdk/src/build_tools/installer_contents.py b/native_client_sdk/src/build_tools/installer_contents.py new file mode 100755 index 0000000..cf26893 --- /dev/null +++ b/native_client_sdk/src/build_tools/installer_contents.py @@ -0,0 +1,181 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""White-list of directories and files that get installed in the SDK. + +Note that the list of directories is kept separate from the list of files +because copying a directory is treated differently than copying files on +Windows. +""" + +import os +import sys +from nacl_sdk_scons import nacl_utils + + +# For each path in this list, its entire contents are added to the SDK +# installer. Directories are denoted by a trailing '/' - this is important +# because they have to be treated separately on Windows for plain files (the +# copy mechanism is different). Note that the path separator is hard-coded to +# be '/'. When accessing these lists, use the Get*() functions, which replace +# the '/' with the correct platform-specific path separator as defined by +# os.path. +INSTALLER_CONTENTS = [ + 'build_tools/nacl_sdk_scons/make_nacl_env.py', + 'build_tools/nacl_sdk_scons/nacl_utils.py', + 'build_tools/nacl_sdk_scons/site_tools/', + 'examples/build.scons', + 'examples/common/', + 'examples/favicon.ico', + 'examples/geturl/', + 'examples/hello_world/', + 'examples/hello_world_c/', + 'examples/httpd.py', + 'examples/index.html', + 'examples/index_staging.html', + 'examples/input_events/', + 'examples/load_progress/', + 'examples/mouselock/', + 'examples/multithreaded_input_events/', + 'examples/pi_generator/', + 'examples/pong/', + 'examples/scons', + 'examples/sine_synth/', + 'examples/tumbler/', + 'examples/fullscreen_tumbler/', + 'project_templates/README', + 'project_templates/c/', + 'project_templates/cc/', + 'project_templates/html/', + 'project_templates/init_project.py', + 'project_templates/scons', + 'project_templates/vs/', + 'third_party/scons-2.0.1/', +] + +INSTALLER_CONTENTS.append('%s/' % nacl_utils.ToolchainPath(base_dir='', + variant='newlib')) +INSTALLER_CONTENTS.append('%s/' % nacl_utils.ToolchainPath(base_dir='', + variant='glibc')) + +LINUX_ONLY_CONTENTS = [ + 'third_party/ppapi/', +] + +MAC_ONLY_CONTENTS = [ + 'third_party/ppapi/', +] + +WINDOWS_ONLY_CONTENTS = [ + 'examples/httpd.cmd', + 'examples/scons.bat', + 'project_templates/scons.bat', + 'debugger/nacl-gdb_server/x64/Release/', + 'debugger/nacl-gdb_server/Release/', + 'debugger/nacl-bpad/x64/Release/' +] + +# These files are user-readable documentation files, and as such get some +# further processing on Windows (\r\n line-endings and .txt file suffix). +# On non-Windows platforms, the installer content list is the union of +# |INSTALLER_CONTENTS| and |DOCUMENTATION_FILES|. +DOCUMENTATION_FILES = [ + 'AUTHORS', + 'COPYING', + 'LICENSE', + 'NOTICE', + 'README', +] + + +def GetToolchainManifest(toolchain): + '''Get the toolchain manifest file. + + These manifest files are used to create NSIS file sections for the + toolchains. The manifest files are considered the source of truth for + symbolic links and other filesystem-specific information that is not + discoverable using python in Windows. + + Args: + toolchain: The toolchain variant. Currently supported values are 'newlib' + and 'glibc'. + + Returns: + The os-specific path to the toolchain manifest file, relative to the SDK's + src directory. + ''' + WINDOWS_TOOLCHAIN_MANIFESTS = { + 'newlib': 'naclsdk_win_x86.tgz.manifest', + 'glibc': 'toolchain_win_x86.tar.xz.manifest', + } + MAC_TOOLCHAIN_MANIFESTS = { + 'newlib': 'naclsdk_mac_x86.tgz.manifest', + 'glibc': 'toolchain_mac_x86.tar.bz2.manifest', + } + LINUX_TOOLCHAIN_MANIFESTS = { + 'newlib': 'naclsdk_linux_x86.tgz.manifest', + 'glibc': 'toolchain_linux_x86.tar.xz.manifest', + } + manifest_file = None + if sys.platform == 'win32': + manifest_file = WINDOWS_TOOLCHAIN_MANIFESTS[toolchain] + elif sys.platform == 'darwin': + manifest_file = MAC_TOOLCHAIN_MANIFESTS[toolchain] + elif sys.platform == 'linux2': + manifest_file = LINUX_TOOLCHAIN_MANIFESTS[toolchain] + if manifest_file: + return os.path.join('build_tools', 'toolchain_archives', manifest_file) + else: + return None + + +def ConvertToOSPaths(path_list): + '''Convert '/' path separators to OS-specific path separators. + + For each file in |path_list|, replace each occurence of the '/' path + separator to the OS-specific separator as defined by os.path. + + Args: + path_list: A list of file paths that use '/' as the path sparator. + + Returns: + A new list where each element represents the same file paths as in + |path_list|, but using the os-specific path separator. + ''' + return [os.path.join(*path.split('/')) for path in path_list] + + +def GetDirectoriesFromPathList(path_list): + '''Return a list of all the content directories. + + The paths in the returned list are formatted to be OS-specific, and are + ready to be used in file IO operations. + + Args: + path_list: A list of paths that use '/' as the path separator. + + Returns: + A list of paths to be included in the SDK installer. The paths all have + OS-specific separators. + ''' + return ConvertToOSPaths( + [dir for dir in path_list if dir.endswith('/')]) + + +def GetFilesFromPathList(path_list): + '''Return a list of all the content files. + + The paths in the returned list are formatted to be OS-specific, and are + ready to be used in file IO operations. + + Args: + path_list: A list of paths that use '/' as the path separator. + + Returns: + A list of paths to be included in the SDK installer. The paths all have + OS-specific separators. + ''' + return ConvertToOSPaths( + [dir for dir in path_list if not dir.endswith('/')]) diff --git a/native_client_sdk/src/build_tools/json/naclsdk_manifest.json b/native_client_sdk/src/build_tools/json/naclsdk_manifest.json new file mode 100644 index 0000000..24d0a02 --- /dev/null +++ b/native_client_sdk/src/build_tools/json/naclsdk_manifest.json @@ -0,0 +1,193 @@ +{ + "bundles": [ + { + "name": "sdk_tools", + "description": "Native Client SDK Tools, revision 1.12", + "stability": "stable", + "recommended": "yes", + "version": 1, + "archives": [ + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/1.12/sdk_tools.tgz", + "checksum": { + "sha1": "abb4c35ac08611e671acc825d1722c1792c94c8e" + }, + "host_os": "mac", + "size": 29088 + }, + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/1.12/sdk_tools.tgz", + "checksum": { + "sha1": "abb4c35ac08611e671acc825d1722c1792c94c8e" + }, + "host_os": "linux", + "size": 29088 + }, + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/1.12/sdk_tools.tgz", + "checksum": { + "sha1": "abb4c35ac08611e671acc825d1722c1792c94c8e" + }, + "host_os": "win", + "size": 29088 + } + ], + "revision": 12 + }, + { + "name": "gdb_builds", + "description": "gdb (Gnu Debugger) build for debugging x86-64 binary from win64 host", + "stability": "beta", + "recommended": "no", + "version": 0, + "archives": [ + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/gdb_builds/0.1/gdb-win-builds.tgz", + "checksum": { + "sha1": "94c78870edcd50f95bb0db040252e766bb275c50" + }, + "host_os": "win", + "size": 34727612 + } + ], + "revision": 1 + }, + { + "name": "pepper_14", + "description": "Chrome 14 bundle, revision 1052", + "stability": "post_stable", + "recommended": "no", + "version": 14, + "archives": [ + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/pepper_14_1052/naclsdk_mac.tgz", + "checksum": { + "sha1": "e4b12fcb2dde33ff8b56e9ac3b83830a46205880" + }, + "host_os": "mac", + "size": 51242839 + }, + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/pepper_14_1052/naclsdk_linux.tgz", + "checksum": { + "sha1": "52e6dd6efbd060956ac62b20dc3ba4bf0d34bcca" + }, + "host_os": "linux", + "size": 55819823 + }, + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/pepper_14_1052/naclsdk_win.exe", + "checksum": { + "sha1": "9451dae079d19c7c5b270a4557aeec5d964858b7" + }, + "host_os": "win", + "size": 18970313 + } + ], + "revision": 1052 + }, + { + "name": "pepper_15", + "description": "Chrome 15 bundle, revision 1239", + "stability": "stable", + "recommended": "yes", + "version": 15, + "archives": [ + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/pepper_15_1239/naclsdk_mac.tgz", + "checksum": { + "sha1": "605888f7d0e1055dd0be9a7366b61467fcb74804" + }, + "host_os": "mac", + "size": 149170725 + }, + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/pepper_15_1239/naclsdk_linux.tgz", + "checksum": { + "sha1": "9a118167b20fe7ada0c4cd735e7d7200970b9b01" + }, + "host_os": "linux", + "size": 151245433 + }, + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/pepper_15_1239/naclsdk_win.exe", + "checksum": { + "sha1": "0843e07989a9cf4afebde75a4b15cfaf2804fd27" + }, + "host_os": "win", + "size": 58275621 + } + ], + "revision": 1239 + }, + { + "name": "pepper_16", + "description": "Chrome 16 bundle, revision 1364", + "stability": "beta", + "recommended": "no", + "version": 16, + "archives": [ + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/pepper_16_1364/naclsdk_mac.tgz", + "checksum": { + "sha1": "4a3cbc1c4d590bb3ea05647e41637b19655006cf" + }, + "host_os": "mac", + "size": 150305285 + }, + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/pepper_16_1364/naclsdk_linux.tgz", + "checksum": { + "sha1": "3d067c3c388081a6d561293ed1b7990fb3aac259" + }, + "host_os": "linux", + "size": 151528839 + }, + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/pepper_16_1364/naclsdk_win.exe", + "checksum": { + "sha1": "687fad7b2b59812dbf65ca2808234a3d3caee350" + }, + "host_os": "win", + "size": 66730335 + } + ], + "revision": 1364 + }, + { + "name": "pepper_17", + "description": "Chrome 17 bundle, revision 1368", + "stability": "dev", + "recommended": "no", + "version": 17, + "archives": [ + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/pepper_17_1368/naclsdk_mac.tgz", + "checksum": { + "sha1": "5639fbcf758d112fddcaec0475354a055015b950" + }, + "host_os": "mac", + "size": 141618408 + }, + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/pepper_17_1368/naclsdk_linux.tgz", + "checksum": { + "sha1": "a400319ed68fb07184f4ab46f1b87df9bb07d49d" + }, + "host_os": "linux", + "size": 140484500 + }, + { + "url": "http://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/pepper_17_1368/naclsdk_win.exe", + "checksum": { + "sha1": "2c30d26670969075d0106181c1406033b10ae3f7" + }, + "host_os": "win", + "size": 70688802 + } + ], + "revision": 1368 + } + ], + "manifest_version": 1 +} diff --git a/native_client_sdk/src/build_tools/make_nacl_tools.py b/native_client_sdk/src/build_tools/make_nacl_tools.py new file mode 100755 index 0000000..834031f --- /dev/null +++ b/native_client_sdk/src/build_tools/make_nacl_tools.py @@ -0,0 +1,213 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Build NaCl tools (e.g. sel_ldr and ncval) at a given revision.""" + +import build_utils +import optparse +import os +import shutil +import subprocess +import sys +import tempfile + +bot = build_utils.BotAnnotator() + + +# The suffix used for NaCl moduels that are installed, such as irt_core. +NEXE_SUFFIX = '.nexe' + +def MakeInstallDirs(options): + '''Create the necessary install directories in the SDK staging area. + ''' + install_dir = os.path.join(options.toolchain, 'bin'); + if not os.path.exists(install_dir): + os.makedirs(install_dir) + runtime_dir = os.path.join(options.toolchain, 'runtime'); + if not os.path.exists(runtime_dir): + os.makedirs(runtime_dir) + + +def Build(options): + '''Build 32-bit and 64-bit versions of needed NaCL tools and libs.''' + nacl_dir = os.path.join(options.nacl_dir, 'native_client') + toolchain_option = 'naclsdk_mode=custom:%s' % options.toolchain + libc_option = '' if options.lib == 'newlib' else ' --nacl_glibc' + if sys.platform == 'win32': + scons = os.path.join(nacl_dir, 'scons.bat') + bits32 = 'vcvarsall.bat x86 && ' + bits64 = 'vcvarsall.bat x86_amd64 && ' + else: + scons = os.path.join(nacl_dir, 'scons') + bits32 = '' + bits64 = '' + + # Build sel_ldr and ncval. + def BuildTools(prefix, bits, target): + cmd = '%s%s -j %s --mode=%s platform=x86-%s naclsdk_validate=0 %s %s%s' % ( + prefix, scons, options.jobs, options.variant, bits, target, + toolchain_option, libc_option) + bot.Run(cmd, shell=True, cwd=nacl_dir) + + BuildTools(bits32, '32', 'sdl=none sel_ldr ncval') + BuildTools(bits64, '64', 'sdl=none sel_ldr ncval') + + # Build irt_core, which is needed for running .nexes with sel_ldr. + def BuildIRT(bits): + cmd = '%s -j %s irt_core --mode=opt-host,nacl platform=x86-%s %s' % ( + scons, options.jobs, bits, toolchain_option) + bot.Run(cmd, shell=True, cwd=nacl_dir) + + # only build the IRT using the newlib chain. glibc does not support IRT. + if options.lib == 'newlib': + BuildIRT(32) + BuildIRT(64) + + # Build and install untrusted libraries. + def BuildAndInstallLibsAndHeaders(bits): + cmd = ('%s install --mode=opt-host,nacl libdir=%s includedir=%s ' + 'platform=x86-%s force_sel_ldr=none %s%s') % ( + scons, + os.path.join(options.toolchain, + 'x86_64-nacl', + 'lib32' if bits == 32 else 'lib'), + os.path.join(options.toolchain, 'x86_64-nacl', 'include'), + bits, + toolchain_option, + libc_option) + bot.Run(cmd, shell=True, cwd=nacl_dir) + + BuildAndInstallLibsAndHeaders(32) + BuildAndInstallLibsAndHeaders(64) + + +def Install(options, tools=[], runtimes=[]): + '''Install the NaCl tools and runtimes into the SDK staging area. + + Assumes that all necessary artifacts are built into the NaCl scons-out/staging + directory, and copies them from there into the SDK staging area under + toolchain. + + Args: + options: The build options object. This is populated from command-line + args at start-up. + tools: A list of tool names, these should *not* have any executable + suffix - this utility adds that (e.g. '.exe' on Windows). + runtimes: A list of IRT runtimes. These artifacts should *not* have any + suffix attached - this utility adds the '.nexe' suffix along with an + ISA-specific string (e.g. '_x86_32'). + ''' + # TODO(bradnelson): add an 'install' alias to the main build for this. + nacl_dir = os.path.join(options.nacl_dir, 'native_client') + tool_build_path_32 = os.path.join(nacl_dir, + 'scons-out', + '%s-x86-32' % (options.variant), + 'staging') + tool_build_path_64 = os.path.join(nacl_dir, + 'scons-out', + '%s-x86-64' % (options.variant), + 'staging') + + for nacl_tool in tools: + shutil.copy(os.path.join(tool_build_path_32, + '%s%s' % (nacl_tool, options.exe_suffix)), + os.path.join(options.toolchain, + 'bin', + '%s_x86_32%s' % (nacl_tool, options.exe_suffix))) + shutil.copy(os.path.join(tool_build_path_64, + '%s%s' % (nacl_tool, options.exe_suffix)), + os.path.join(options.toolchain, + 'bin', + '%s_x86_64%s' % (nacl_tool, options.exe_suffix))) + + irt_build_path_32 = os.path.join(nacl_dir, + 'scons-out', + 'nacl_irt-x86-32', + 'staging') + irt_build_path_64 = os.path.join(nacl_dir, + 'scons-out', + 'nacl_irt-x86-64', + 'staging') + for nacl_irt in runtimes: + shutil.copy(os.path.join(irt_build_path_32, + '%s%s' % (nacl_irt, NEXE_SUFFIX)), + os.path.join(options.toolchain, + 'runtime', + '%s_x86_32%s' % (nacl_irt, NEXE_SUFFIX))) + shutil.copy(os.path.join(irt_build_path_64, + '%s%s' % (nacl_irt, NEXE_SUFFIX)), + os.path.join(options.toolchain, + 'runtime', + '%s_x86_64%s' % (nacl_irt, NEXE_SUFFIX))) + + +def BuildNaClTools(options): + if(options.clean): + bot.Print('Removing scons-out') + scons_out = os.path.join(options.nacl_dir, 'native_client', 'scons-out') + build_utils.CleanDirectory(scons_out) + else: + MakeInstallDirs(options) + Build(options) + Install(options, tools=['sel_ldr', 'ncval'], runtimes=['irt_core']) + return 0 + + +def main(argv): + if sys.platform in ['win32', 'cygwin']: + exe_suffix = '.exe' + else: + exe_suffix = '' + + script_dir = os.path.abspath(os.path.dirname(__file__)) + + parser = optparse.OptionParser() + parser.add_option( + '-t', '--toolchain', dest='toolchain', + default='toolchain', + help='where to put the NaCl tool binaries') + parser.add_option( + '-l', '--lib', dest='lib', + default='newlib', + help='whether to build against newlib (default) or glibc') + parser.add_option( + '-c', '--clean', action='store_true', dest='clean', + default=False, + help='whether to clean up the checkout files') + parser.add_option( + '-j', '--jobs', dest='jobs', default='1', + help='Number of parallel jobs to use while building nacl tools') + parser.add_option( + '-n', '--nacl_dir', dest='nacl_dir', + default=os.path.join(script_dir, 'packages', 'native_client'), + help='Location of Native Client repository used for building tools') + (options, args) = parser.parse_args(argv) + if args: + parser.print_help() + bot.Print('ERROR: invalid argument(s): %s' % args) + return 1 + + options.toolchain = os.path.abspath(options.toolchain) + options.exe_suffix = exe_suffix + # Pick variant. + if sys.platform in ['win32', 'cygwin']: + variant = 'dbg-win' + elif sys.platform == 'darwin': + variant = 'dbg-mac' + elif sys.platform in ['linux', 'linux2']: + variant = 'dbg-linux' + else: + assert False + options.variant = variant + + if options.lib not in ['newlib', 'glibc']: + bot.Print('ERROR: --lib must either be newlib or glibc') + return 1 + + return BuildNaClTools(options) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/native_client_sdk/src/build_tools/make_nsis_installer.py b/native_client_sdk/src/build_tools/make_nsis_installer.py new file mode 100644 index 0000000..38035bd --- /dev/null +++ b/native_client_sdk/src/build_tools/make_nsis_installer.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Create the NSIS installer for the SDK.""" + +import os +import subprocess + +from build_tools import build_utils +from build_tools import install_nsis +from build_tools import nsis_script + +# TODO(dspringer): |toolchain_manifests| is not currently used by any callers. +def MakeNsisInstaller(installer_dir, + sdk_version=None, + cwd=None, + toolchain_manifests=None): + '''Create the NSIS installer + + Args: + installer_dir: The directory containing all the artifacts that get packaged + in the NSIS installer. + + sdk_version: A string representing the SDK version. The string is expected + to be a '.'-separated triple representing <major>.<minor>.<build>. If + this argument is None, then the default is the value returned by + build_utils.RawVersion() + + cwd: The current working directory. Various artifacts (such as the NSIS + installer) are expected to be in this directory. Defaults to the + script's directory. + + toolchain_manifests: A dictionary of manifests for things in the + toolchain directory. The dictionary can have these keys: + 'files': a set of plain files + 'dirs': a set of directories + 'symlinks': a dictionary of symbolic links + 'links': a dictionary of hard links. + For more details on these sets and dictionaries, please see the + tar_archive module. + ''' + if not sdk_version: + sdk_version = build_utils.RawVersion() + sdk_full_name = 'native_client_sdk_%s' % sdk_version.replace('.', '_') + + if not cwd: + cwd = os.path.abspath(os.path.dirname(__file__)) + + install_nsis.Install(cwd) + script = nsis_script.NsisScript(os.path.join(cwd, 'make_sdk_installer.nsi')) + script.install_dir = os.path.join('C:%s' % os.sep, sdk_full_name) + script.InitFromDirectory(installer_dir) + if toolchain_manifests: + toolchain_manifests.PrependPath(installer_dir) + script |= toolchain_manifests + script.Compile() diff --git a/native_client_sdk/src/build_tools/make_sdk_installer.nsi b/native_client_sdk/src/build_tools/make_sdk_installer.nsi new file mode 100644 index 0000000..9cae0af --- /dev/null +++ b/native_client_sdk/src/build_tools/make_sdk_installer.nsi @@ -0,0 +1,103 @@ +!include "FileFunc.nsh" +!include "LogicLib.nsh" +!include "MUI2.nsh" +!include "Sections.nsh" +!include "x64.nsh" + +RequestExecutionLevel user +SetCompressor /solid lzma +SetCompressorDictSize 128 +Name "Native Client SDK" +OutFile ../../nacl-sdk.exe + +; The full SDK install name is generated from the version string. +!include sdk_install_name.nsh + +Var SVV_CmdLineParameters +Var SVV_SelChangeInProgress + +!define MUI_HEADERIMAGE +!define MUI_WELCOMEFINISHPAGE_BITMAP \ + "${NSISDIR}\Contrib\Graphics\Wizard\win.bmp" + +!define MUI_WELCOMEPAGE_TITLE "Welcome to the Native Client SDK Setup Wizard" +!define MUI_WELCOMEPAGE_TEXT "This wizard will guide you through the \ +installation of the Native Client SDK $\r$\n$\r$\nThe Native Client SDK \ +includes a GNU toolchain adopted for Native Client use and some examples. You \ +need Google Chrome to test the examples.$\r$\n$\r$\nYou will also need to \ +install Python (please visit www.python.org/download)$\r$\n$\r$\n$_CLICK" + +!define MUI_COMPONENTSPAGE_SMALLDESC + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES + +!define MUI_FINISHPAGE_LINK \ + "Visit the Native Client site for news, FAQs and support" +!define MUI_FINISHPAGE_LINK_LOCATION \ + "http://code.google.com/chrome/nativeclient" + +!define MUI_FINISHPAGE_SHOWREADME +!define MUI_FINISHPAGE_SHOWREADME_TEXT "Show release notes" +!define MUI_FINISHPAGE_SHOWREADME_FUNCTION ShowReleaseNotes + +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_LANGUAGE "English" + +Section "" sec_Preinstall + Push $R0 + CreateDirectory "$INSTDIR" + ; Owner can do anything + AccessControlW::GrantOnFile "$INSTDIR" "(S-1-3-0)" "FullAccess" + ; Group can read + AccessControlW::GrantOnFile "$INSTDIR" "(S-1-3-1)" "Traverse + GenericRead" + ; "Everyone" can read too + AccessControlW::GrantOnFile "$INSTDIR" "(S-1-1-0)" "Traverse + GenericRead" + FileClose $R0 + Pop $R0 +SectionEnd + +; The SDK Section commands are in a generated file. +!include sdk_section.nsh + +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT \ + ${NativeClientSDK} \ + "Native Client SDK - toolchain and examples" +!insertmacro MUI_FUNCTION_DESCRIPTION_END + +Function .onInit + ${GetParameters} $SVV_CmdLineParameters + Push $R0 + ClearErrors + ${GetOptions} $SVV_CmdLineParameters "/?" $R0 + IfErrors +1 HelpMessage + ${GetOptions} $SVV_CmdLineParameters "--help" $R0 + IfErrors +3 +1 +HelpMessage: + MessageBox MB_OK "Recognized common options:$\n \ + /D=InstDir - use InstDir as target instead of usual $INSTDIR$\n \ + /NCRC - disables the CRC check$\n \ + /S - Silent install" + Abort + Pop $R0 +FunctionEnd + +Function .onSelChange + ${If} $SVV_SelChangeInProgress == 0 + StrCpy $SVV_SelChangeInProgress 1 + Push $R0 + IntOp $R0 ${SF_SELECTED} | ${SF_BOLD} + SectionSetFlags ${NativeClientSDK} $R0 + Pop $R0 + StrCpy $SVV_SelChangeInProgress 0 + ${EndIf} +FunctionEnd + +Function ShowReleaseNotes + ExecShell "open" \ + "http://code.google.com/chrome/nativeclient/docs/releasenotes.html" +FunctionEnd diff --git a/native_client_sdk/src/build_tools/make_sdk_tools.py b/native_client_sdk/src/build_tools/make_sdk_tools.py new file mode 100755 index 0000000..7981883 --- /dev/null +++ b/native_client_sdk/src/build_tools/make_sdk_tools.py @@ -0,0 +1,135 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Create the base (auto)updater for the Native Client SDK""" + +import py_compile +import optparse +import os +import shutil +import sys +import tarfile +import zipfile + +from build_tools.sdk_tools import update_manifest +from build_tools.sdk_tools import sdk_update + +NACL_SDK = 'nacl_sdk' + + +def ZipDirectory(dirpath, zippath): + '''Create a zipfile from the contents of a given directory. + + The path of the resulting contents in the zipfile will match that of the + last directory name in dirpath. + + Args: + dirpath: Path to directory to add to zipfile + zippath: filename of resulting zipfile + ''' + zip = None + try: + zip = zipfile.ZipFile(zippath, 'w', zipfile.ZIP_DEFLATED) + basedir = '%s%s' % (os.path.dirname(dirpath), os.sep) + for root, dirs, files in os.walk(dirpath): + if os.path.basename(root)[0] == '.': + continue # skip hidden directories + dirname = root.replace(basedir, '') + for file in files: + zip.write(os.path.join(root, file), os.path.join(dirname, file)) + finally: + if zip: + zip.close() + + +def WriteTarFile(outname, in_dir, tar_dir=''): + '''Create a new compressed tarball from a given directory + + Args: + outname: path and filename of the gzipped tar file + in_dir: source directory that will be tar'd and gzipped + tar_dir: root directory within the tarball''' + tar_file = None + try: + tar_file = tarfile.open(outname, 'w:gz') + tar_file.add(in_dir, tar_dir) + finally: + if tar_file: + tar_file.close() + + +def MakeSdkTools(nacl_sdk_filename, sdk_tools_filename): + '''Make the nacl_sdk and sdk_tools tarballs + + The nacl_sdk package contains these things: + + nacl_sdk/ + naclsdk(.bat) - The main entry point for updating the SDK + sdk_tools/ + sdk_update.py - Performs the work in checking for updates + python/ + python.exe - (optional) python executable, shipped with Windows + ... - other python files and directories + sdk_cache/ + naclsdk_manifest.json - manifest file with information about sdk_tools + + Args: + nacl_sdk_filename: name of zipfile that the user directly downloads + sdk_tools_filename: name of tarball that has the sdk_tools directory + ''' + base_dir = os.path.abspath(os.path.dirname(__file__)) + base_dir_parent = os.path.dirname(base_dir) + temp_dir = os.path.join(base_dir, NACL_SDK) + if os.path.exists(temp_dir): + shutil.rmtree(temp_dir) + os.mkdir(temp_dir) + for dir in ['sdk_tools', 'sdk_cache']: + os.mkdir(os.path.join(temp_dir, dir)) + shutil.copy2(os.path.join(base_dir, 'naclsdk'), temp_dir) + shutil.copy2(os.path.join(base_dir, 'naclsdk.bat'), temp_dir) + with open(os.path.join(base_dir_parent, 'LICENSE'), "U") as source_file: + text = source_file.read().replace("\n", "\r\n") + with open(os.path.join(temp_dir, 'sdk_tools', 'LICENSE'), "wb") as dest_file: + dest_file.write(text) + + tool_list = ['sdk_update.py', 'set_nacl_env.py'] + for tool in tool_list: + shutil.copy2(os.path.join(base_dir, 'sdk_tools', tool), + os.path.join(temp_dir, 'sdk_tools')) + py_compile.compile(os.path.join(temp_dir, 'sdk_tools', tool)) + + update_manifest_options = [ + '--bundle-revision=%s' % sdk_update.MINOR_REV, + '--bundle-version=%s' % sdk_update.MAJOR_REV, + '--description=Native Client SDK Tools, revision %s.%s' % ( + sdk_update.MAJOR_REV, sdk_update.MINOR_REV), + '--bundle-name=sdk_tools', + '--recommended=yes', + '--stability=stable', + '--manifest-version=%s' % sdk_update.SDKManifest().MANIFEST_VERSION, + '--manifest-file=%s' % + os.path.join(temp_dir, 'sdk_cache', 'naclsdk_manifest.json')] + if 0 != update_manifest.main(update_manifest_options): + raise Exception('update_manifest terminated abnormally.') + ZipDirectory(temp_dir, nacl_sdk_filename) + WriteTarFile(sdk_tools_filename, os.path.join(temp_dir, 'sdk_tools'), '') + shutil.rmtree(temp_dir, ignore_errors=True) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option( + '-n', '--nacl-sdk', dest='nacl_sdk', default='nacl_sdk.zip', + help='name of the resulting nacl_sdk zipfile') + parser.add_option( + '-s', '--sdk-tools', dest='sdk_tools', default='sdk_tools.tgz', + help='name of the resulting sdk_tools tarball') + (options, args) = parser.parse_args(argv) + MakeSdkTools(options.nacl_sdk, options.sdk_tools) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/native_client_sdk/src/build_tools/nacl_sdk_scons/__init__.py b/native_client_sdk/src/build_tools/nacl_sdk_scons/__init__.py new file mode 100644 index 0000000..33dca08 --- /dev/null +++ b/native_client_sdk/src/build_tools/nacl_sdk_scons/__init__.py @@ -0,0 +1,10 @@ +#! -*- python -*- + +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""build_tools/nacl_sdk_scons Package + +This package contains general python scons utilities that are used for +creating the Native Client SDK.""" diff --git a/native_client_sdk/src/build_tools/nacl_sdk_scons/make_nacl_env.py b/native_client_sdk/src/build_tools/nacl_sdk_scons/make_nacl_env.py new file mode 100644 index 0000000..acff50d --- /dev/null +++ b/native_client_sdk/src/build_tools/nacl_sdk_scons/make_nacl_env.py @@ -0,0 +1,205 @@ +# -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +'''Construct an Environment that uses the NaCl toolchain to build C/C++ code. +The base dir for the NaCl toolchain is in the NACL_SDK_ROOT environment +variable. +''' + +import nacl_utils +import os + +from SCons import Script + +def NaClEnvironment(use_c_plus_plus_libs=False, + nacl_platform=None, + toolchain_arch=None, + toolchain_variant=None, + use_ppapi=True, + install_subdir=None, + lib_prefix=None): + '''Make an Environment that uses the NaCl toolchain to build sources. + + This modifies a default construction Environment to point the compilers and + other bintools at the NaCl-specific versions, adds some tools that set certain + build flags needed by the NaCl-specific tools, and adds a custom Builder that + generates .nmf files. + + Args: + use_c_plus_plus_libs: Indicate whether to insert the C++ NaCl libs at the + right place in the list of LIBS. + nacl_platform: The target NaCl/Chrome/Papper platform for which the + environment, e.g. 'pepper_14'. + toolchain_arch: The target architecture of the toolchain (e.g., x86, pnacl) + toolchain_variant: The libc of the toolchain (e.g., newlib, glibc) + use_ppapi: flag indicating whether to compile again ppapi libraries + install_subdir: subdirectory within the NACL_INSTALL_ROOT for this project. + lib_prefix: an optional list of path components to prepend to the library + path. These components are joined with appropriate path separators + Examples: ['..', '..'], ['..', 'peer_directory']. + Returns: + A SCons Environment with all the various Tool and keywords set to build + NaCl modules. + ''' + + def GetCommandLineOption(option_name, option_value, option_default): + '''Small helper function to get a command line option. + + Returns a command-line option value, which can be overridden. If the + option is set on the command line, then that value is favoured over the + internally set value. If option is neither set on the command line nor + given a value, its default is used. + + Args: + option_name: The name of the command line option, e.g. "variant". + option_value: The initial value of the option. This value is used if the + its not set via the command line. Can be None. + option_default: If the option value hasn't been set via the command line + nor via an internal value, then this default value is used. Can be + None. + + Returns: + The value of the command-line option, according to the override rules + described above. + ''' + cmd_line_value = Script.GetOption(option_name) + if not cmd_line_value and not option_value: + cmd_line_value = option_default + return cmd_line_value or option_value + + nacl_utils.AddCommandLineOptions() + env = Script.Environment() + + # We must have a nacl_platform, either as argument to this function or from + # the command line. However, if we're cleaning we can relax this requirement. + # (And our build bots will be much happier that way.) + nacl_platform_from_option = Script.GetOption('nacl_platform') + if not nacl_platform_from_option and not nacl_platform: + if Script.GetOption('clean'): + nacl_platform_from_option='.' + else: + raise ValueError('NaCl platform not specified') + + # Setup the base dir for tools, etc. Favor the nacl platform specified on + # the command line if there's a conflict. + nacl_platform_to_use = nacl_platform_from_option or nacl_platform + + toolchain_variant = GetCommandLineOption( + 'variant', toolchain_variant, nacl_utils.DEFAULT_TOOLCHAIN_VARIANT) + toolchain_arch = GetCommandLineOption( + 'architecture', toolchain_arch, nacl_utils.DEFAULT_TOOLCHAIN_ARCH) + + base_dir = os.getenv('NACL_SDK_ROOT', '') + base_dir = os.path.join(base_dir, nacl_platform_to_use) + toolchain = nacl_utils.ToolchainPath(base_dir=base_dir, + arch=toolchain_arch, + variant=toolchain_variant) + if (toolchain is None): + raise ValueError('Cannot find a NaCl toolchain') + + tool_bin_path = os.path.join(toolchain, 'bin') + tool_runtime_path = os.path.join(toolchain, 'runtime') + staging_dir = os.path.abspath(os.getenv( + 'NACL_INSTALL_ROOT', os.path.join(os.getenv('NACL_SDK_ROOT', '.'), + 'staging'))) + if install_subdir: + staging_dir = os.path.join(staging_dir, install_subdir) + lib_prefix = lib_prefix or [] + if type(lib_prefix) is not list: + # Break path down into list of directory components + lib_prefix = filter(lambda x:x, lib_prefix.split('/')) + + # Invoke the various *nix tools that the NativeClient SDK resembles. This + # is done so that SCons doesn't try to invoke cl.exe on Windows in the + # Object builder. + env.Tool('g++') + env.Tool('gcc') + env.Tool('gnulink') + env.Tool('ar') + env.Tool('as') + + env.Tool('nacl_tools') + # TODO(dspringer): Figure out how to make this dynamic and then compute it + # based on the desired target arch. + env.Replace(tools=['nacl_tools'], + # Replace the normal unix tools with the NaCl ones. Note the + # use of the NACL_ARCHITECTURE prefix for the tools. This + # Environment variable is set in nacl_tools.py; it has no + # default value. + CC=os.path.join(tool_bin_path, '${NACL_ARCHITECTURE}gcc'), + CXX=os.path.join(tool_bin_path, '${NACL_ARCHITECTURE}g++'), + AR=os.path.join(tool_bin_path, '${NACL_ARCHITECTURE}ar'), + AS=os.path.join(tool_bin_path, '${NACL_ARCHITECTURE}as'), + GDB=os.path.join(tool_bin_path, '${NACL_ARCHITECTURE}gdb'), + # NOTE: use g++ for linking so we can handle C AND C++. + LINK=os.path.join(tool_bin_path, '${NACL_ARCHITECTURE}g++'), + LD=os.path.join(tool_bin_path, '${NACL_ARCHITECTURE}ld'), + STRIP=os.path.join(tool_bin_path, '${NACL_ARCHITECTURE}strip'), + NACL_SEL_LDR32=os.path.join(tool_bin_path, 'sel_ldr_x86_32'), + NACL_IRT_CORE32=os.path.join(tool_runtime_path, + 'irt_core_x86_32.nexe'), + NACL_SEL_LDR64=os.path.join(tool_bin_path, 'sel_ldr_x86_64'), + NACL_IRT_CORE64=os.path.join(tool_runtime_path, + 'irt_core_x86_64.nexe'), + RANLIB=os.path.join(tool_bin_path, '${NACL_ARCHITECTURE}ranlib'), + ASFLAGS=['${EXTRA_ASFLAGS}', + ], + # c specific + EXTRA_CFLAGS=[], + CFLAGS=['${EXTRA_CFLAGS}', + '-std=gnu99', + ], + # c++ specific + EXTRA_CXXFLAGS=[], + CXXFLAGS=['${EXTRA_CXXFLAGS}', + '-std=gnu++98', + '-Wno-long-long', + ], + # Both C and C++ + CCFLAGS=['${EXTRA_CCFLAGS}', + '-Wall', + '-Wswitch-enum', + '-pthread', + ], + CPPDEFINES=[# _GNU_SOURCE ensures that strtof() gets declared. + ('_GNU_SOURCE', 1), + # This ensures that PRId64 etc. get defined. + ('__STDC_FORMAT_MACROS', '1'), + # strdup, and other common stuff + ('_BSD_SOURCE', '1'), + ('_POSIX_C_SOURCE', '199506'), + ('_XOPEN_SOURCE', '600'), + ], + CPPPATH=[], + LINKFLAGS=['${EXTRA_LINKFLAGS}', + ], + # The NaCl environment makes '.nexe' executables. If this is + # not explicitly set, then SCons on Windows doesn't understand + # how to construct a Program builder properly. + PROGSUFFIX='.nexe', + # Target NaCl platform info. + TARGET_NACL_PLATFORM=nacl_platform_to_use, + NACL_TOOLCHAIN_VARIANT=toolchain_variant, + NACL_TOOLCHAIN_ROOT=toolchain, + NACL_INSTALL_ROOT=staging_dir, + NACL_LIB_PREFIX=lib_prefix, + ) + # This supresses the "MS_DOS style path" warnings on Windows. It's benign on + # all other platforms. + env['ENV']['CYGWIN'] = 'nodosfilewarning' + + # Append the common NaCl libs. + if use_ppapi: + common_nacl_libs = ['ppapi'] + if use_c_plus_plus_libs: + common_nacl_libs.extend(['ppapi_cpp']) + env.Append(LIBS=common_nacl_libs) + + gen_nmf_builder = env.Builder(suffix='.nmf', + action=nacl_utils.GenerateNmf) + env.Append(BUILDERS={'GenerateNmf': gen_nmf_builder}) + + return env diff --git a/native_client_sdk/src/build_tools/nacl_sdk_scons/nacl_utils.py b/native_client_sdk/src/build_tools/nacl_sdk_scons/nacl_utils.py new file mode 100644 index 0000000..3e012fa --- /dev/null +++ b/native_client_sdk/src/build_tools/nacl_sdk_scons/nacl_utils.py @@ -0,0 +1,495 @@ +# -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +'''Small utility library of python functions used by the various helper +scripts. +''' +# Needed for Python 2.5 -- Unnecessary (but harmless) for 2.6 +from __future__ import with_statement + +import optparse +import os +import sys + +from SCons import Script +from site_tools import create_nmf + +#------------------------------------------------------------------------------ +# Parameters + +# The newlib toolchain variant. .nexes built with this variant require a +# different manifest file format than those built with glibc. +NEWLIB_TOOLCHAIN_VARIANT = 'newlib' + +# The default toolchain architecture. +DEFAULT_TOOLCHAIN_ARCH = 'x86' + +# The default toolchain variant. +DEFAULT_TOOLCHAIN_VARIANT = NEWLIB_TOOLCHAIN_VARIANT + +# Map the string stored in |sys.platform| into a toolchain host specifier. +# @private +__PLATFORM_TO_HOST_MAP = { + 'win32': 'windows', + 'cygwin': 'windows', + 'linux2': 'linux', + 'darwin': 'mac' +} + +# Map a platform host to a map of possible architectures and toolchains. +# The platform host can be derived from sys.platform using |PLATFORM_HOST_MAP_}| +# (see above). (Note: if this map changes, you must also update the copy in +# set_nacl_env.py.) +# TODO(gwink): This map is duplicated in set_nacl_env.py. Find a good way to +# share it instead. +# @private +__HOST_TO_TOOLCHAIN_MAP = { + 'windows': { + 'x86': { + 'glibc': 'win_x86', + 'newlib': 'win_x86_newlib', + }, + }, + 'linux': { + 'x86': { + 'glibc': 'linux_x86', + 'newlib': 'linux_x86_newlib', + }, + 'pnacl': { + 'newlib-32': 'pnacl_linux_i686_newlib', + 'newlib-64': 'pnacl_linux_x86_64_newlib', + 'glibc-64': 'pnacl_linux_x86_64_glibc', + }, + 'arm': { + 'newlib': 'pnacl_linux_x86_64_newlib', + 'glibc': 'pnacl_linux_x86_64_glibc', + }, + }, + 'mac': { + 'x86': { + 'glibc': 'mac_x86', + 'newlib': 'mac_x86_newlib', + }, + 'pnacl': { + 'newlib': 'pnacl_darwin_i386_newlib', + }, + }, +} + +# Various architecture spec objects suitable for use with +# nacl_env_ext.SetArchFlags() +ARCH_SPECS = { + 'x86-32': { + 'arch': 'x86', + 'subarch': '32' + }, + 'x86-64': { + 'arch': 'x86', + 'subarch': '64' + }, + 'arm': { + 'arch': 'ARM', + 'subarch': '' + } +} + +# Default values for 'arch' and 'subarch'. +DEFAULT_ARCH = 'x86' +DEFAULT_SUBARCH = '32' + +#------------------------------------------------------------------------------ +# Functions + +def AddCommandLineOptions(): + '''Register the cmd-line options. + + Registers --nacl-platform, --architecture and --variant. This function can be + called multiple times. Only the first call registers the option. + + Args: + None + + Returns: + None + ''' + + try: + Script.AddOption( + '--nacl-platform', + dest='nacl_platform', + nargs=1, + type='string', + action='store', + help='target pepper version') + Script.Help(' --nacl-platform ' + 'Specify the pepper version to build for ' + '(e.g. --nacl-platform="pepper_14").\n') + + Script.AddOption( + '--architecture', + dest='architecture', + nargs=1, + type='string', + action='store', + help='NaCl target architecture') + Script.Help(' --architecture ' + 'Specify the NaCl target architecture to build (e.g. ' + '--architecture="glibc"). Possible values are "x86", "pnacl" ' + '"arm". Not all target architectures are available on all ' + 'host platforms. Defaults to "x86"\n') + + Script.AddOption( + '--variant', + dest='variant', + nargs=1, + type='string', + action='store', + help='NaCl toolchain variant') + Script.Help(' --variant ' + 'Specify the NaCl toolchain variant to use when ' + 'building (e.g. --variant="glibc"). Possible values are ' + '"glibc", "newlib"; when --architecture=pnacl is specified, ' + 'values must include bit-width, e.g. "glibc-64". Defaults to ' + '"newlib"\n') + + except optparse.OptionConflictError: + pass + + +def PrintNaclPlatformBanner(module_name, nacl_platform, variant): + '''Print a banner that shows what nacl platform is used to build a module. + + Args: + module_name: The name of the module. Printed as-is. + nacl_platform: The name - a.k.a. folder name - of the nacl platform. + Printed as-is. + variant: The toolchain variant, one of 'newlib', 'glibc', 'pnacl', etc. + Returns: + None + ''' + + # Don't print the banner if we're just cleaning files. + if not Script.GetOption('clean'): + print '---------------------------------------------------------------' + print ('+ Project "%s" is using NaCl platform "%s", toolchain "%s"' % + (module_name, nacl_platform, variant)) + print '---------------------------------------------------------------' + print '' + sys.stdout.flush() + + +def ToolchainPath(base_dir=None, + arch=DEFAULT_TOOLCHAIN_ARCH, + variant=DEFAULT_TOOLCHAIN_VARIANT): + '''Build a toolchain path based on the platform type. + + |base_dir| is the root directory which includes the platform-specific + toolchain. This could be something like "/usr/local/mydir/nacl_sdk/src". If + |base_dir| is None, then the environment variable NACL_SDK_ROOT is used (if + it's set). This method assumes that the platform-specific toolchain is found + under <base_dir>/toolchain/<platform_spec>. + + Args: + base_dir: The pathname of the root directory that contains the toolchain. + The toolchain is expected to be in a dir called 'toolchain' + within |base_dir|. + variant: The toolchain variant, can be one of 'newlib', 'glibc', etc. + Defaults to 'newlib'. + Returns: + The concatenated platform-specific path to the toolchain. This will look + like base_dir/toolchain/mac_x86 + ''' + + if base_dir is None: + base_dir = os.getenv('NACL_SDK_ROOT', '') + if sys.platform in __PLATFORM_TO_HOST_MAP: + host_platform = __PLATFORM_TO_HOST_MAP[sys.platform] + toolchain_map = __HOST_TO_TOOLCHAIN_MAP[host_platform] + if arch in toolchain_map: + isa_toolchain = toolchain_map[arch] + if variant in isa_toolchain: + return os.path.normpath(os.path.join( + base_dir, 'toolchain', isa_toolchain[variant])) + else: + raise ValueError('ERROR: Variant "%s" not in toolchain "%s/%s".' % + (variant, host_platform, arch)) + else: + raise ValueError('ERROR: Architecture "%s" not supported on host "%s".' % + (arch, host_platform)) + + else: + raise ValueError('ERROR: Unsupported host platform "%s".' % sys.platform) + + +def GetJSONFromNexeSpec(nexe_spec): + '''Generate a JSON string that represents the architecture-to-nexe mapping + in |nexe_spec|. + + The nexe spec is a simple dictionary, whose keys are architecture names and + values are the nexe files that should be loaded for the corresponding + architecture. For example: + {'x86-32': 'hello_world_x86_32.nexe', + 'x86-64': 'hello_world_x86_64.nexe', + 'arm': 'hello_world_ARM.nexe'} + + Args: + nexe_spec: The dictionary that maps architectures to .nexe files. + Returns: + A JSON string representing |nexe_spec|. + ''' + nmf_json = '{\n' + nmf_json += ' "program": {\n' + + # Add an entry in the JSON for each specified architecture. Note that this + # loop emits a trailing ',' for every line but the last one. + if nexe_spec and len(nexe_spec): + line_count = len(nexe_spec) + for arch_key in nexe_spec: + line_count -= 1 + eol_char = ',' if line_count > 0 else '' + nmf_json += ' "%s": {"url": "%s"}%s\n' % (arch_key, + nexe_spec[arch_key], + eol_char) + + nmf_json += ' }\n' + nmf_json += '}\n' + return nmf_json + + +def GenerateNmf(target, source, env): + '''This function is used to create a custom Builder that produces .nmf files. + + The .nmf files are given in the list of targets. This expects the .nexe + mapping to be given as the value of the 'nexes' keyword in |env|. To add + this function as a Builder, use this SCons code: + gen_nmf_builder = nacl_env.Builder(suffix='.nmf', + action=nacl_utils.GenerateNmf) + nacl_env.Append(BUILDERS={'GenerateNmf': gen_nmf_builder}) + To invoke the Builder, do this, for example: + # See examples/hello_world/build.scons for more details. + hello_world_opt_nexes = [nacl_env.NaClProgram(....), ....] + nacl_env.GenerateNmf(target='hello_world.nmf', + source=hello_world_opt_nexes, + nexes={'x86-32': 'hello_world_x86_32.nexe', + 'x86-64': 'hello_world_x86_64.nexe', + 'arm': 'hello_world_ARM.nexe'}) + + A Builder that invokes this function is added to the NaCl Environment by + the NaClEnvironment() function in make_nacl_env.py + + Args: + target: The list of targets to build. This is expected to be a list of + File Nodes that point to the required .nmf files. + source: The list of sources that the targets depend on. This is typically + a list of File Nodes that represent .nexes + env: The SCons construction Environment that provides the build context. + Returns: + None on success. Raises a ValueError() if there are missing parameters, + such as the 'nexes' keyword in |env|. + ''' + + if target == None or source == None: + raise ValueError('No value given for target or source.') + + nexes = env.get('nexes', []) + if len(nexes) == 0: + raise ValueError('No value for "nexes" keyword.') + + for target_file in target: + # If any of the following functions raises an exception, just let the + # exception bubble up to the calling context. This will produce the + # correct SCons error. + target_path = target_file.get_abspath() + if env['NACL_TOOLCHAIN_VARIANT'] == NEWLIB_TOOLCHAIN_VARIANT: + nmf_json = GetJSONFromNexeSpec(nexes) + else: + nmf = create_nmf.NmfUtils( + objdump=os.path.join(env['NACL_TOOLCHAIN_ROOT'], 'bin', + 'x86_64-nacl-objdump'), + main_files=[str(file) for file in source], + lib_path=[os.path.join(env['NACL_TOOLCHAIN_ROOT'], 'x86_64-nacl', dir) + for dir in ['lib', 'lib32']], + lib_prefix=env['NACL_LIB_PREFIX']) + nmf_json = nmf.GetJson() + if env.get('NACL_INSTALL_ROOT', None): + nmf.StageDependencies(env['NACL_INSTALL_ROOT']) + with open(target_path, 'w') as nmf_file: + nmf_file.write(nmf_json) + + # Return None to indicate success. + return None + + +def GetArchFromSpec(arch_spec): + '''Pick out the values for 'arch' and 'subarch' from |arch_spec|, providing + default values in case nothing is specified. + + Args: + arch_spec: An object that can have keys 'arch' and 'subarch'. + Returns: + A tuple (arch, subarch) that contains either the values of the + corresponding keys in |arch_spec| or a default value. + ''' + if arch_spec == None: + return (DEFAULT_ARCH, DEFAULT_SUBARCH) + arch = arch_spec.get('arch', DEFAULT_ARCH) + subarch = arch_spec.get('subarch', DEFAULT_SUBARCH) + return (arch, subarch) + + +def GetArchName(arch_spec): + ''' Return a name of the form arch_subarch for the given arch spec. + + Args: + arch_spec: An object containing 'arch' and 'subarch' keys that describe + the instruction set architecture of the output program. See + |ARCH_SPECS| in nacl_utils.py for valid examples. + + Returns: + A string with the arch name. + ''' + return '%s_%s' % GetArchFromSpec(arch_spec) + + +def MakeNaClCommonEnvironment(nacl_env, + arch_spec=ARCH_SPECS['x86-32'], + is_debug=False): + '''Make a clone of nacl_env that is suitable for building programs or + libraries for a specific build variant. + + Make a cloned NaCl Environment and setup variables for options like optimized + versus debug CCFLAGS. + + Args: + nacl_env: A SCons construction environment. This is typically the return + value of NaClEnvironment() (see above). + arch_spec: An object containing 'arch' and 'subarch' keys that describe + the instruction set architecture of the output program. See + |ARCH_SPECS| in nacl_utils.py for valid examples. + is_debug: Indicates whether this program should be built for debugging or + optimized. + + Returns: + A SCons Environment setup with options for the specified variant of a NaCl + module or library. + ''' + arch_name = GetArchName(arch_spec) + env = nacl_env.Clone() + env.AppendOptCCFlags(is_debug) + env.AppendArchFlags(arch_spec) + + # Wrap linker command with TEMPFILE so that if lines are longer than + # MAXLINELENGTH, the tools will be run with @tmpfile. This isn't needed + # for any of the sdk examples, but if people cargo cult them for other + # purposes, they can end up hitting command line limits on Windows where + # MAXLINELENGTH can be as low as 2048. + env['LINKCOM'] = '${TEMPFILE("' + env['LINKCOM'] + '")}' + env['SHLINKCOM'] = '${TEMPFILE ' + env['SHLINKCOM'] + '")}' + + return env + + +def MakeNaClModuleEnvironment(nacl_env, + sources, + module_name='nacl', + arch_spec=ARCH_SPECS['x86-32'], + is_debug=False, + build_dir_prefix=''): + '''Make a NaClProgram Node for a specific build variant. + + Make a NaClProgram Node in a cloned Environment. Set the environment + in the cloned Environment variables for things like optimized versus debug + CCFLAGS, and also adds a Program builder that makes a NaCl module. The name + of the module is derived from |module_name|, |arch_spec| and |is_debug|; for + example: + MakeNaClModuleEnvironment(nacl_env, sources, module_name='hello_world', + arch_spec=nacl_utils.ARCH_SPECS['x86-64'], is_debug=True) + will produce a NaCl module named + hello_world_x86_64_dbg.nexe + + Args: + nacl_env: A SCons construction environment. This is typically the return + value of NaClEnvironment() (see above). + sources: A list of source Nodes used to build the NaCl module. + module_name: The name of the module. The name of the output program + incorporates this as its prefix. + arch_spec: An object containing 'arch' and 'subarch' keys that describe + the instruction set architecture of the output program. See + |ARCH_SPECS| in nacl_utils.py for valid examples. + is_debug: Indicates whether this program should be built for debugging or + optimized. + build_dir_prefix: Allows user to prefix the build directory with an + additional string. + + Returns: + A SCons Environment that builds the specified variant of a NaCl module. + ''' + debug_name = 'dbg' if is_debug else 'opt' + arch_name = GetArchName(arch_spec) + env = MakeNaClCommonEnvironment(nacl_env, arch_spec, is_debug) + return env.NaClProgram('%s_%s%s' % (module_name, + arch_name, + '_dbg' if is_debug else ''), + sources, + variant_dir='%s%s_%s' % + (build_dir_prefix, debug_name, arch_name)) + + +def MakeNaClStaticLibEnvironment(nacl_env, + sources, + lib_name='nacl', + arch_spec=ARCH_SPECS['x86-32'], + is_debug=False, + build_dir_prefix='', + lib_dir=''): + '''Make a NaClStaticLib Node for a specific build variant. + + Make a NaClStaticLib Node in a cloned Environment. Set the environment + in the cloned Environment variables for things like optimized versus debug + CCFLAGS, and also adds a Program builder that makes a NaCl static library. + The name of the library is derived from |lib_name|, |arch_spec| and + |is_debug|; for example: + MakeNaClStaticLibEnvironment(nacl_env, sources, lib_name='c_salt', + arch_spec=nacl_utils.ARCH_SPECS['x86-64'], is_debug=True) + will produce a NaCl static library named + libc_salt_x86_64_dbg.a + + Args: + nacl_env: A SCons construction environment. This is typically the return + value of NaClEnvironment() (see above). + sources: A list of source Nodes used to build the NaCl library. + lib_name: The name of the library. + arch_spec: An object containing 'arch' and 'subarch' keys that describe + the instruction set architecture of the output program. See + |ARCH_SPECS| in nacl_utils.py for valid examples. + is_debug: Indicates whether this program should be built for debugging or + optimized. + build_dir_prefix: Allows user to prefix the build directory with an + additional string. + lib_dir: Where to output the final library file. Lib files are placed in + directories within lib_dir, based on the arch type and build type. + E.g. Specifying lib_dir = 'dest' for lib name 'c_salt' on x86-32 debug + creates the library file 'dest/lib-x86-32/dbg/libc_salt.a' + + Returns: + A SCons Environment that builds the specified variant of a NaCl module. + ''' + env = MakeNaClCommonEnvironment(nacl_env, arch_spec, is_debug) + debug_name = 'dbg' if is_debug else 'opt' + arch_name = GetArchName(arch_spec) + target_name = '%s_%s%s' % (lib_name, arch_name, debug_name) + variant_dir = '%s%s_%s' % (build_dir_prefix, debug_name, arch_name) + + arch, subarch = GetArchFromSpec(arch_spec) + lib_subdir = 'lib-' + arch + if subarch: lib_subdir = lib_subdir + '-' + subarch + lib_dir = os.path.join(lib_dir, lib_subdir, debug_name) + + return env.NaClStaticLib(target_name, + sources, + variant_dir, + lib_name, + lib_dir) diff --git a/native_client_sdk/src/build_tools/nacl_sdk_scons/nacl_utils_test.py b/native_client_sdk/src/build_tools/nacl_sdk_scons/nacl_utils_test.py new file mode 100755 index 0000000..015f98f --- /dev/null +++ b/native_client_sdk/src/build_tools/nacl_sdk_scons/nacl_utils_test.py @@ -0,0 +1,111 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for nacl_utils.py.""" + +import fileinput +import mox +import nacl_utils +import os +import sys +import unittest + + +def TestMock(file_path, open_func): + temp_file = open_func(file_path) + temp_file.close() + + +class TestNaClUtils(unittest.TestCase): + """Class for test cases to cover globally declared helper functions.""" + + def setUp(self): + self.script_dir = os.path.abspath(os.path.dirname(__file__)) + self.mock_factory = mox.Mox() + self.InitializeResourceMocks() + + def InitializeResourceMocks(self): + """Can be called multiple times if multiple functions need to be tested.""" + self.fileinput_mock = self.mock_factory.CreateMock(fileinput) + self.os_mock = self.mock_factory.CreateMock(os) + self.sys_mock = self.mock_factory.CreateMock(sys) + + def testToolchainPath(self): + output = nacl_utils.ToolchainPath('nacl_sdk_root') + head, tail = os.path.split(output) + base, toolchain = os.path.split(head) + self.assertEqual('nacl_sdk_root', base) + self.assertEqual('toolchain', toolchain) + self.assertRaises(ValueError, + nacl_utils.ToolchainPath, + 'nacl_sdk_root', + arch='nosucharch') + self.assertRaises(ValueError, + nacl_utils.ToolchainPath, + 'nacl_sdk_root', + variant='nosuchvariant') + + def testGetJSONFromNexeSpec(self): + valid_empty_json = '{\n "program": {\n }\n}\n' + null_json = nacl_utils.GetJSONFromNexeSpec(None) + self.assertEqual(null_json, valid_empty_json) + empty_json = nacl_utils.GetJSONFromNexeSpec({}) + self.assertEqual(empty_json, valid_empty_json) + nexes = {'x86-32': 'nacl_x86_32.nexe', + 'x86-64': 'nacl_x86_64.nexe', + 'arm': 'nacl_ARM.nexe'} + json = nacl_utils.GetJSONFromNexeSpec(nexes) + # Assert that the resulting JSON has all the right parts: the "nexes" + # dict, followed by one entry for each architecture. Also make sure that + # the last entry doesn't have a trailing ',' + json_lines = json.splitlines() + self.assertEqual(len(json_lines), 7) + self.assertEqual(json_lines[0], '{') + self.assertEqual(json_lines[1], ' "program": {') + self.assertTrue(json_lines[2].endswith(',')) + self.assertTrue(json_lines[3].endswith(',')) + self.assertFalse(json_lines[4].endswith(',')) + self.assertEqual(json_lines[5], ' }') + self.assertEqual(json_lines[6], '}') + # Assert that the key-value pair lines have the right form. The order + # of the keys doesn't matter. Note that the key values are enclosed in + # "" (e.g. "x86-32") - this is intentional. + valid_arch_keys = ['"x86-32"', '"x86-64"', '"arm"'] + for line in json_lines[2:4]: + key_value = line.split(':') + self.assertEqual(len(key_value), 3) + self.assertTrue(key_value[0].lstrip().rstrip() in valid_arch_keys) + + def testGenerateNmf(self): + # Assert that failure cases properly fail. + self.assertRaises(ValueError, nacl_utils.GenerateNmf, None, None, None) + self.assertRaises(ValueError, nacl_utils.GenerateNmf, [], [], {}) + + def testGetArchFromSpec(self): + default_arch, default_subarch = nacl_utils.GetArchFromSpec(None) + self.assertEqual(default_arch, nacl_utils.DEFAULT_ARCH) + self.assertEqual(default_subarch, nacl_utils.DEFAULT_SUBARCH) + default_arch, subarch = nacl_utils.GetArchFromSpec({'subarch': '64'}) + self.assertEqual(default_arch, nacl_utils.DEFAULT_ARCH) + self.assertEqual(subarch, '64') + arch, default_subarch = nacl_utils.GetArchFromSpec({'arch': 'x86'}) + self.assertEqual(arch, 'x86') + self.assertEqual(default_subarch, nacl_utils.DEFAULT_SUBARCH) + arch, subarch = nacl_utils.GetArchFromSpec({'arch': 'x86', 'subarch': '64'}) + self.assertEqual(arch, 'x86') + self.assertEqual(subarch, '64') + + +def RunTests(): + return_value = 1 + test_suite = unittest.TestLoader().loadTestsFromTestCase(TestNaClUtils) + test_results = unittest.TextTestRunner(verbosity=2).run(test_suite) + if test_results.wasSuccessful(): + return_value = 0 + return return_value + +if __name__ == '__main__': + sys.exit(RunTests()) diff --git a/native_client_sdk/src/build_tools/nacl_sdk_scons/nmf_test.py b/native_client_sdk/src/build_tools/nacl_sdk_scons/nmf_test.py new file mode 100644 index 0000000..4788890 --- /dev/null +++ b/native_client_sdk/src/build_tools/nacl_sdk_scons/nmf_test.py @@ -0,0 +1,196 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for nmf.py.""" + +import exceptions +import json +import optparse +import os +import subprocess +import sys +import tempfile +import unittest + +from build_tools import build_utils +from build_tools.nacl_sdk_scons.site_tools import create_nmf + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +X86_32 = 'x86-32' +X86_64 = 'x86-64' +ARCHS = [X86_32, X86_64] + + +def CallCreateNmf(args): + '''Calls the create_nmf.py utility and returns stdout as a string + + Args: + args: command-line arguments as a list (not including program name) + + Returns: + string containing stdout + + Raises: + subprocess.CalledProcessError: non-zero return code from sdk_update''' + command = [sys.executable, os.path.join(SCRIPT_DIR, 'site_tools', + 'create_nmf.py')] + args + process = subprocess.Popen(stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + args=command) + output, error_output = process.communicate() + + retcode = process.poll() # Note - calling wait() can cause a deadlock + if retcode != 0: + print "\nCallCreateNmf(%s)" % command + print "stdout=%s\nstderr=%s" % (output, error_output) + sys.stdout.flush() + raise subprocess.CalledProcessError(retcode, command) + return output + + +def GlobalInit(options, temp_files): + '''Global initialization for testing nmf file creation. + + Args: + options: object containing gcc and gpp defines + files: (output) A list that will contain all generated nexes. Each + nexe is a value in a map, keyed by architecture. + + Returns: + A list of filemaps, where each filemap is a dict with key=architecture + and value=filename + ''' + hello_world_c = ( + '#include <stdio.h>\n' + 'int main(void) { printf("Hello World!\\n"); return 0; }\n') + hello_world_cc = ( + '#include <iostream>\n' + 'int main(void) { std::cout << "Hello World!\\n"; return 0; }\n') + + files = [] + def MakeNexe(contents, suffix, compiler): + file_map = {} + for arch in ARCHS: + source_filename = None + with tempfile.NamedTemporaryFile(delete=False, + suffix=suffix) as temp_file: + source_filename = temp_file.name + temp_files.append(source_filename) + temp_file.write(contents) + nexe_filename = os.path.splitext(source_filename)[0] + '.nexe' + file_map[arch] = nexe_filename + temp_files.append(nexe_filename) + subprocess.check_call([compiler, + '-m32' if arch == X86_32 else '-m64', + source_filename, + '-o', nexe_filename]) + files.append(file_map) + + c_file = MakeNexe(hello_world_c, '.c', options.gcc) + cc_file = MakeNexe(hello_world_cc, '.cc', options.gpp) + return files + + +def TestingClosure(toolchain_dir, file_map): + '''Closure to provide variables to the test cases + + Args: + toolchain_dir: path to toolchain that we are testing + file_map: dict of nexe files by architecture. + ''' + + class TestNmf(unittest.TestCase): + ''' Test basic functionality of the sdk_update package ''' + + def setUp(self): + self.objdump = (os.path.join(toolchain_dir, + 'bin', + 'x86_64-nacl-objdump')) + self.library_paths = [os.path.join(toolchain_dir, 'x86_64-nacl', lib) + for lib in ['lib32', 'lib']] + self.lib_options = ['--library-path=%s' % lib + for lib in self.library_paths] + + def testRunCreateNmf(self): + json_text = CallCreateNmf( + self.lib_options + ['--objdump', self.objdump] + file_map.values()) + obj = json.loads(json_text) + # For now, just do a simple sanity check that there is a file + # and program section. + self.assertTrue(obj.get('files')) + self.assertTrue(obj.get('program')) + for arch in ['x86-32', 'x86-64']: + self.assertEqual(obj['program'][arch]['url'], + '%s/runnable-ld.so' % arch) + self.assertEqual(obj['files']['main.nexe'][arch]['url'], + os.path.basename(file_map[arch])) + for filename, rest in obj['files'].items(): + if filename == 'main.nexe': + continue + self.assertEqual('/'.join([arch, filename]), + rest[arch]['url']) + + def testGenerateManifest(self): + nmf = create_nmf.NmfUtils( + objdump=self.objdump, + main_files=file_map.values(), + lib_path=self.library_paths) + nmf_json = nmf.GetManifest() + for arch in ['x86-32', 'x86-64']: + self.assertEqual(nmf_json['program'][arch]['url'], + '%s/runnable-ld.so' % arch) + self.assertEqual(nmf_json['files']['main.nexe'][arch]['url'], + os.path.basename(file_map[arch])) + for filename, rest in nmf_json['files'].items(): + if filename == 'main.nexe': + continue + self.assertEqual('/'.join([arch, filename]), + rest[arch]['url']) + + return TestNmf + + +def GlobalTeardown(temp_files): + '''Remove all the temporary files in temp_files''' + for filename in temp_files: + if os.path.exists(filename): + os.remove(filename) + + +def main(argv): + '''Usage: %prog [options] + + Runs the unit tests on the nmf utility''' + parser = optparse.OptionParser(usage=main.__doc__) + + parser.add_option( + '-t', '--toolchain-dir', dest='toolchain_dir', + help='(required) root directory of toolchain') + + (options, args) = parser.parse_args(argv) + + options.gcc = os.path.join(options.toolchain_dir, 'bin', 'x86_64-nacl-gcc') + options.gpp = os.path.join(options.toolchain_dir, 'bin', 'x86_64-nacl-g++') + options.objdump = os.path.join(options.toolchain_dir, 'bin', + 'x86_64-nacl-objdump') + + success = True + temp_files = [] + try: + nexe_maps = GlobalInit(options, temp_files) + for file_map in nexe_maps: + suite = unittest.TestLoader().loadTestsFromTestCase( + TestingClosure(toolchain_dir=options.toolchain_dir, + file_map=file_map)) + result = unittest.TextTestRunner(verbosity=2).run(suite) + success = result.wasSuccessful() and success + finally: + GlobalTeardown(temp_files) + + return int(not success) # 0 = success, 1 = failure + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/native_client_sdk/src/build_tools/nacl_sdk_scons/site_tools/__init__.py b/native_client_sdk/src/build_tools/nacl_sdk_scons/site_tools/__init__.py new file mode 100644 index 0000000..33d78a0 --- /dev/null +++ b/native_client_sdk/src/build_tools/nacl_sdk_scons/site_tools/__init__.py @@ -0,0 +1,10 @@ +#! -*- python -*- + +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""build_tools/nacl_sdk_scons/site_tools Package + +This package contains general python scons utilities that are used for +creating the Native Client SDK.""" diff --git a/native_client_sdk/src/build_tools/nacl_sdk_scons/site_tools/create_nmf.py b/native_client_sdk/src/build_tools/nacl_sdk_scons/site_tools/create_nmf.py new file mode 100755 index 0000000..e055809 --- /dev/null +++ b/native_client_sdk/src/build_tools/nacl_sdk_scons/site_tools/create_nmf.py @@ -0,0 +1,328 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from __future__ import with_statement + +import errno +import optparse +import os +import re +import shutil +import subprocess +import sys +import urllib + +try: + import json +except ImportError: + import simplejson as json + +NeededMatcher = re.compile('^ *NEEDED *([^ ]+)\n$') +FormatMatcher = re.compile('^(.+):\\s*file format (.+)\n$') + +FORMAT_ARCH_MAP = { + # Names returned by Linux's objdump: + 'elf64-x86-64': 'x86-64', + 'elf32-i386': 'x86-32', + # Names returned by x86_64-nacl-objdump: + 'elf64-nacl': 'x86-64', + 'elf32-nacl': 'x86-32', + # TODO(mball): Add support for 'arm-32' and 'portable' architectures + # 'elf32-little': 'arm-32', + } + +# These constants are used within nmf files. +RUNNABLE_LD = 'runnable-ld.so' # Name of the dynamic loader +MAIN_NEXE = 'main.nexe' # Name of entry point for execution +PROGRAM_KEY = 'program' # Key of the program section in an nmf file +URL_KEY = 'url' # Key of the url field for a particular file in an nmf file +FILES_KEY = 'files' # Key of the files section in an nmf file + +# The proper name of the dynamic linker, as kept in the IRT. This is +# excluded from the nmf file by convention. +LD_NACL_MAP = { + 'x86-32': 'ld-nacl-x86-32.so.1', + 'x86-64': 'ld-nacl-x86-64.so.1', +} + +_debug_mode = False # Set to True to enable extra debug prints + + +def DebugPrint(message): + if _debug_mode: + sys.stderr.write('%s\n' % message) + sys.stderr.flush() + + +class Error(Exception): + '''Local Error class for this file.''' + pass + + +class ArchFile(object): + '''Simple structure containing information about + + Attributes: + arch: Architecture of this file (e.g., x86-32) + filename: name of this file + path: Full path to this file on the build system + url: Relative path to file in the staged web directory. + Used for specifying the "url" attribute in the nmf file.''' + def __init__(self, arch, name, path='', url=None): + self.arch = arch + self.name = name + self.path = path + self.url = url or '/'.join([arch, name]) + + def __str__(self): + '''Return the file path when invoked with the str() function''' + return self.path + + +class NmfUtils(object): + '''Helper class for creating and managing nmf files + + Attributes: + manifest: A JSON-structured dict containing the nmf structure + needed: A dict with key=filename and value=ArchFile (see GetNeeded) + ''' + + def __init__(self, main_files=None, objdump='x86_64-nacl-objdump', + lib_path=None, extra_files=None, lib_prefix=None): + ''' Constructor + + Args: + main_files: List of main entry program files. These will be named + files->main.nexe for dynamic nexes, and program for static nexes + objdump: path to x86_64-nacl-objdump tool (or Linux equivalent) + lib_path: List of paths to library directories + extra_files: List of extra files to include in the nmf + lib_prefix: A list of path components to prepend to the library paths, + both for staging the libraries and for inclusion into the nmf file. + Examples: ['..'], ['lib_dir'] ''' + self.objdump = objdump + self.main_files = main_files or [] + self.extra_files = extra_files or [] + self.lib_path = lib_path or [] + self.manifest = None + self.needed = None + self.lib_prefix = lib_prefix or [] + + def GleanFromObjdump(self, files): + '''Get architecture and dependency information for given files + + Args: + files: A dict with key=filename and value=list or set of archs. E.g.: + { '/path/to/my.nexe': ['x86-32', 'x86-64'], + '/path/to/libmy.so': ['x86-32'], + '/path/to/my2.nexe': None } # Indicates all architectures + + Returns: A tuple with the following members: + input_info: A dict with key=filename and value=ArchFile of input files. + Includes the input files as well, with arch filled in if absent. + Example: { '/path/to/my.nexe': ArchFile(my.nexe), + '/path/to/libfoo.so': ArchFile(libfoo.so) } + needed: A set of strings formatted as "arch/name". Example: + set(['x86-32/libc.so', 'x86-64/libgcc.so']) + ''' + DebugPrint("GleanFromObjdump(%s)" % ([self.objdump, '-p'] + files.keys())) + proc = subprocess.Popen([self.objdump, '-p'] + files.keys(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, bufsize=-1) + input_info = {} + needed = set() + output, err_output = proc.communicate() + for line in output.splitlines(True): + # Objdump should display the architecture first and then the dependencies + # second for each file in the list. + matched = FormatMatcher.match(line) + if matched is not None: + filename = matched.group(1) + arch = FORMAT_ARCH_MAP[matched.group(2)] + if files[filename] is None or arch in files[filename]: + name = os.path.basename(filename) + input_info[filename] = ArchFile( + arch=arch, + name=name, + path=filename, + url='/'.join(self.lib_prefix + [arch, name])) + matched = NeededMatcher.match(line) + if matched is not None: + if files[filename] is None or arch in files[filename]: + needed.add('/'.join([arch, matched.group(1)])) + status = proc.poll() + if status != 0: + raise Error('%s\nStdError=%s\nobjdump failed with error code: %d' % + (output, err_output, status)) + return input_info, needed + + def FindLibsInPath(self, name): + '''Finds the set of libraries matching |name| within lib_path + + Args: + name: name of library to find + + Returns: + A list of system paths that match the given name within the lib_path''' + files = [] + for dir in self.lib_path: + file = os.path.join(dir, name) + if os.path.exists(file): + files.append(file) + if not files: + raise Error('cannot find library %s' % name) + return files + + def GetNeeded(self): + '''Collect the list of dependencies for the main_files + + Returns: + A dict with key=filename and value=ArchFile of input files. + Includes the input files as well, with arch filled in if absent. + Example: { '/path/to/my.nexe': ArchFile(my.nexe), + '/path/to/libfoo.so': ArchFile(libfoo.so) }''' + if not self.needed: + DebugPrint('GetNeeded(%s)' % self.main_files) + examined = set() + all_files, unexamined = self.GleanFromObjdump( + dict([(file, None) for file in self.main_files])) + for name, arch_file in all_files.items(): + arch_file.url = os.path.basename(name) + if unexamined: + unexamined.add('/'.join([arch_file.arch, RUNNABLE_LD])) + while unexamined: + files_to_examine = {} + for arch_name in unexamined: + arch, name = arch_name.split('/') + for path in self.FindLibsInPath(name): + files_to_examine.setdefault(path, set()).add(arch) + new_files, needed = self.GleanFromObjdump(files_to_examine) + all_files.update(new_files) + examined |= unexamined + unexamined = needed - examined + # With the runnable-ld.so scheme we have today, the proper name of + # the dynamic linker should be excluded from the list of files. + ldso = [LD_NACL_MAP[arch] for arch in set(FORMAT_ARCH_MAP.values())] + for name, arch_map in all_files.items(): + if arch_map.name in ldso: + del all_files[name] + self.needed = all_files + return self.needed + + def StageDependencies(self, destination_dir): + '''Copies over the dependencies into a given destination directory + + Each library will be put into a subdirectory that corresponds to the arch. + + Args: + destination_dir: The destination directory for staging the dependencies + ''' + needed = self.GetNeeded() + for source, arch_file in needed.items(): + destination = os.path.join(destination_dir, + urllib.url2pathname(arch_file.url)) + try: + os.makedirs(os.path.dirname(destination)) + except OSError as exception_info: + if exception_info.errno != errno.EEXIST: + raise + if (os.path.normcase(os.path.abspath(source)) != + os.path.normcase(os.path.abspath(destination))): + shutil.copy2(source, destination) + + def _GenerateManifest(self): + programs = {} + files = {} + + def add_files(needed): + for filename, arch_file in needed.items(): + files.setdefault(arch_file.arch, set()).add(arch_file.name) + + needed = self.GetNeeded() + add_files(needed) + + for filename in self.main_files: + arch_file = needed[filename] + programs[arch_file.arch] = arch_file.name + + filemap = {} + for arch in files: + for file in files[arch]: + if file not in programs.values() and file != RUNNABLE_LD: + filemap.setdefault(file, set()).add(arch) + + def arch_name(arch, file): + # nmf files expect unix-style path separators + return {URL_KEY: '/'.join(self.lib_prefix + [arch, file])} + + # TODO(mcgrathr): perhaps notice a program with no deps + # (i.e. statically linked) and generate program=nexe instead? + manifest = {PROGRAM_KEY: {}, FILES_KEY: {MAIN_NEXE: {}}} + for arch in programs: + manifest[PROGRAM_KEY][arch] = arch_name(arch, RUNNABLE_LD) + manifest[FILES_KEY][MAIN_NEXE][arch] = {URL_KEY: programs[arch]} + + for file in filemap: + manifest[FILES_KEY][file] = dict([(arch, arch_name(arch, file)) + for arch in filemap[file]]) + self.manifest = manifest + + def GetManifest(self): + '''Returns a JSON-formatted dict containing the NaCl dependencies''' + if not self.manifest: + self._GenerateManifest() + + return self.manifest + + def GetJson(self): + '''Returns the Manifest as a JSON-formatted string''' + pretty_string = json.dumps(self.GetManifest(), indent=2) + # json.dumps sometimes returns trailing whitespace and does not put + # a newline at the end. This code fixes these problems. + pretty_lines = pretty_string.split('\n') + return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n' + + +def Main(argv): + parser = optparse.OptionParser( + usage='Usage: %prog [options] nexe [extra_libs...]') + parser.add_option('-o', '--output', dest='output', + help='Write manifest file to FILE (default is stdout)', + metavar='FILE') + parser.add_option('-D', '--objdump', dest='objdump', default='objdump', + help='Use TOOL as the "objdump" tool to run', + metavar='TOOL') + parser.add_option('-L', '--library-path', dest='lib_path', + action='append', default=[], + help='Add DIRECTORY to library search path', + metavar='DIRECTORY') + parser.add_option('-s', '--stage-dependencies', dest='stage_dependencies', + help='Destination directory for staging libraries', + metavar='DIRECTORY') + (options, args) = parser.parse_args(argv) + + if len(args) < 1: + parser.print_usage() + sys.exit(1) + + nmf = NmfUtils(objdump=options.objdump, + main_files=args, + lib_path=options.lib_path) + + manifest = nmf.GetManifest() + + if options.output is None: + sys.stdout.write(nmf.GetJson()) + else: + with open(options.output, 'w') as output: + output.write(nmf.GetJson()) + + if options.stage_dependencies: + nmf.StageDependencies(options.stage_dependencies) + + +# Invoke this file directly for simple testing. +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/native_client_sdk/src/build_tools/nacl_sdk_scons/site_tools/nacl_tools.py b/native_client_sdk/src/build_tools/nacl_sdk_scons/site_tools/nacl_tools.py new file mode 100644 index 0000000..5e50e9e --- /dev/null +++ b/native_client_sdk/src/build_tools/nacl_sdk_scons/site_tools/nacl_tools.py @@ -0,0 +1,487 @@ +# -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +'''Extend the SCons Environment object with NaCl-specific builders and +modifiers. +''' + +from SCons import Script + +import nacl_utils +import os +import SCons + +def FilterOut(env, **kw): + """Removes values from existing construction variables in an Environment. + + The values to remove should be a list. For example: + + env.FilterOut(CPPDEFINES=['REMOVE_ME', 'ME_TOO']) + + Args: + env: Environment to alter. + kw: (Any other named arguments are values to remove). + """ + + kw = SCons.Environment.copy_non_reserved_keywords(kw) + + for key, val in kw.items(): + if key in env: + # Filter out the specified values without modifying the original list. + # This helps isolate us if a list is accidently shared + # NOTE if env[key] is a UserList, this changes the type into a plain + # list. This is OK because SCons also does this in semi_deepcopy + env[key] = [item for item in env[key] if item not in val] + + # TODO: SCons.Environment.Append() has much more logic to deal with various + # types of values. We should handle all those cases in here too. (If + # variable is a dict, etc.) + +def AppendOptCCFlags(env, is_debug=False): + '''Append a set of CCFLAGS that will build a debug or optimized variant + depending on the value of |is_debug|. + + Uses optional build-specific flags for debug and optimized builds. To set + these in your build.scons files you can do something like this: + nacl_env.Append(DEBUG_CCFLAGS=['-gfull'], + OPT_CCFLAGS=['-ffast-math', + '-mfpmath=sse', + '-msse2']) + + Args: + env: Environment to modify. + is_debug: Whether to set the option flags for debugging or not. Default + value is False. + ''' + + if is_debug: + env.Append(CCFLAGS=['${DEBUG_CCFLAGS}', + '-O0', + '-g', + ]) + else: + env.Append(CCFLAGS=['${OPT_CCFLAGS}', + '-O3', + '-fno-stack-protector', + '-fomit-frame-pointer', + ]) + + +def AppendArchFlags(env, arch_spec): + '''Append a set of architecture-specific flags to the environment. + + |arch_spec| is expected to be a map containing the keys "arch" and "subarch". + Supported keys are: + arch: x86 + subarch: 32 | 64 + + Args: + env: Environment to modify. + arch_spec: A dictionary with keys describing the arch and subarch to build. + Possible values are: 'arch': x86; 'subarch': 32 or 64. + ''' + + arch, subarch = nacl_utils.GetArchFromSpec(arch_spec) + cc_arch_flags = ['-m%s' % subarch] + as_arch_flags = ['--%s' % subarch] + if subarch == '64': + ld_arch_flags = ['-m64'] + env['NACL_ARCHITECTURE'] = 'x86_64-nacl-' + else: + ld_arch_flags = ['-m32'] + env['NACL_ARCHITECTURE'] = 'i686-nacl-' + env.Append(ASFLAGS=as_arch_flags, + CCFLAGS=cc_arch_flags, + LINKFLAGS=ld_arch_flags) + + +def NaClProgram(env, target, sources, variant_dir='obj'): + '''Add a Program to env that builds its objects in the directory specified + by |variant_dir|. + + This is slightly different than VariantDir() in that the sources can live in + the same directory as the calling SConscript file. + + Args: + env: Environment to modify. + target: The target name that depends on the object files. E.g. + "hello_world_x86_32.nexe" + sources: The list of source files that are used to build the objects. + variant_dir: The built object files are put in this directory. Default + value is "obj". + + Returns: + The Program Node. + ''' + + program_objects = [] + for src_file in sources: + obj_file = os.path.splitext(src_file)[0] + env.get('OBJSUFFIX', '.o') + program_objects.append(env.StaticObject( + target=os.path.join(variant_dir, obj_file), source=src_file)) + env.Clean('.', variant_dir) + return env.Program(target, program_objects) + + +def NaClTestProgram(env, + test_sources, + arch_spec, + module_name='nacl_test', + target_name='test'): + '''Modify |env| to include an Alias node for a test named |test_name|. + + This node will build the desired NaCl module with the debug flags turned on. + The Alias node has a build action that runs the test under sel_ldr. |env| is + expected to have variables named 'NACL_SEL_LDR<x>', and 'NACL_IRT_CORE<x>' + where <x> is the various architectures supported (e.g. NACL_SEL_LDR32 and + NACL_SEL_LLDR64) + + Args: + env: Environment to modify. + test_sources: The list of source files that are used to build the objects. + arch_spec: A dictionary with keys describing the arch and subarch to build. + Possible values are: 'arch': x86; 'subarch': 32 or 64. + module_name: The name of the module. The name of the output program + incorporates this as its prefix. + target_name: The name of the final Alias node. This name can be given on + the command line. For example: + nacl_env.NaClTestProgram(nacl_utils.ARCH_SPECS['x86-32'], + 'hello_world_test', + 'test32') + will let you say: ./scons test32 to build and run hello_world_test. + Returns: + A list of Nodes, one for each architecture-specific test. + ''' + + arch, subarch = nacl_utils.GetArchFromSpec(arch_spec) + # Create multi-level dictionary for sel_ldr binary name. + NACL_SEL_LDR = {'x86' : + {'32': '$NACL_SEL_LDR32', + '64': '$NACL_SEL_LDR64' + } + } + NACL_IRT_CORE = {'x86' : + {'32': '$NACL_IRT_CORE32', + '64': '$NACL_IRT_CORE64' + } + } + arch_sel_ldr = NACL_SEL_LDR[arch][subarch] + # if |arch| and |subarch| are not found, a KeyError exception will be + # thrown, which will generate a stack trace for debugging. + test_program = nacl_utils.MakeNaClModuleEnvironment( + env, + test_sources, + module_name, + arch_spec, + is_debug=True, + build_dir_prefix='test_') + test_node = env.Alias(target_name, + source=test_program, + action=arch_sel_ldr + + ' -B %s' % NACL_IRT_CORE[arch][subarch] + + ' $SOURCE') + # Tell SCons that |test_node| never goes out of date, so that you don't see + # '<test_node> is up to date.' + env.AlwaysBuild(test_node) + + +def NaClStaticLib(env, target, sources, variant_dir='obj', + lib_name='', lib_dir=''): + '''Add a StaticLibrary to env that builds its objects in the directory + specified by |variant_dir|. + + This is slightly different than VariantDir() in that the sources can live in + the same directory as the calling SConscript file. + + Args: + env: Environment to modify. + target: The target name that depends on the object files. E.g. + "c_salt_x86_32" yields the target name libc_salt_x86_32.a. + sources: The list of source files that are used to build the objects. + variant_dir: The built object files are put in this directory. Default + value is "obj". + lib_name: The final name for the library. E.g. lib_name='c_salt' yields + the final library name libc_)salt.a. Defaults to the target name. + lib_dir: The final library file is placed in that directory. The directory + is created if it doesn't already exists. Default is '.'. + + Returns: + The StaticLibrary Node. + ''' + + program_objects = [] + for src_file in sources: + obj_file = os.path.splitext(src_file)[0] + env.get('OBJSUFFIX', '.o') + program_objects.append(env.StaticObject( + target=os.path.join(variant_dir, obj_file), source=src_file)) + env.Clean('.', variant_dir) + lib_file = env.StaticLibrary(target, program_objects) + + # If either a lib_name or lib_dir, we must move the library file to its + # final destination. + if lib_dir or lib_name: + # Map lib_name to an actual file name that includes the correct prefix and + # suffix, both extracted from the target library name generated by scons. + if lib_name: + final_lib_name = lib_file[0].name.replace(target, lib_name) + else: + final_lib_name = lib_file[0].name + # Add an action to move the file. + install_node = env.InstallAs(os.path.join(lib_dir, final_lib_name), + lib_file) + # Add lib_name as an alias. This is the target that will build and install + # the libraries. + if lib_name: + env.Alias(lib_name, install_node) + + return lib_file + + +def NaClStrippedInstall(env, dir='', source=None): + '''Strip the target node. + + Args: + env: Environment to modify. + dir: The root install directory. + source: a list of a list of Nodes representing the executables to be + stripped. + + Returns: + A list of Install Nodes that strip each buildable in |source|. + ''' + stripped_install_nodes = [] + if not source: + return stripped_install_nodes + for source_nodes in source: + for strip_node in source_nodes: + # Use the construction Environment used to create the buildable object. + # Each environment has various important properties, such as the target + # architecture and tools prefix. + strip_env = strip_node.get_env() + install_node = strip_env.Install(dir=strip_env['NACL_INSTALL_ROOT'], + source=strip_node) + strip_env.AddPostAction(install_node, "%s $TARGET" % strip_env['STRIP']) + stripped_install_nodes.append(install_node) + + return stripped_install_nodes + + +def NaClModules(env, sources, module_name, is_debug=False): + '''Produce one construction Environment for each supported instruction set + architecture. + + Args: + env: Environment to modify. + sources: The list of source files that are used to build the objects. + module_name: The name of the module. + is_debug: Whether to set the option flags for debugging or not. Default + value is False. + + Returns: + A list of SCons build Nodes, each one with settings specific to an + instruction set architecture. + ''' + return [ + nacl_utils.MakeNaClModuleEnvironment( + env, + sources, + module_name=module_name, + arch_spec=nacl_utils.ARCH_SPECS['x86-32'], + is_debug=is_debug), + nacl_utils.MakeNaClModuleEnvironment( + env, + sources, + module_name=module_name, + arch_spec=nacl_utils.ARCH_SPECS['x86-64'], + is_debug=is_debug), + ] + + +def NaClStaticLibraries(env, sources, lib_name, is_debug=False, lib_dir=''): + '''Produce one static-lib construction Environment for each supported + instruction set architecture. + + Args: + env: Environment to modify. + sources: The list of source files that are used to build the objects. + lib_name: The name of the static lib. + is_debug: Whether to set the option flags for debugging or not. Default + value is False. + lib_dir: Where to output the final library file. Lib files are placed in + directories within lib_dir, based on the arch type and build type. + E.g. Specifying lib_dir = 'dest' for lib name 'c_salt' on x86-32 debug + creates the library file 'dest/lib-x86-32/dbg/libc_salt.a' + + Returns: + A list of SCons build Nodes, each one with settings specific to an + instruction set architecture. + ''' + return [ + nacl_utils.MakeNaClStaticLibEnvironment( + env, + sources, + lib_name=lib_name, + arch_spec=nacl_utils.ARCH_SPECS['x86-32'], + is_debug=is_debug, + lib_dir=lib_dir), + nacl_utils.MakeNaClStaticLibEnvironment( + env, + sources, + lib_name=lib_name, + arch_spec=nacl_utils.ARCH_SPECS['x86-64'], + is_debug=is_debug, + lib_dir=lib_dir), + ] + + +def InstallPrebuilt(env, module_name): + '''Create the 'install_prebuilt' target. + + install_prebuilt is used by the SDK build machinery to provide a prebuilt + version of the example in the SDK installer. This pseudo-builder adds an + Alias node called 'install_prebuilt' that depends on the main .nmf file of + the example. The .nmf file in turn has all the right dependencies to build + the necessary NaCl modules. As a final step, the opt variant directories + are removed. Once this build is done, the SDK builder can include the + example directory in its installer. + + Args: + env: Environment to modify. + module_name: The name of the module. + + Returns: + The Alias node representing the install_prebuilt target. + ''' + + return env.Alias('install_prebuilt', + source=['%s.nmf' % module_name], + action=Script.Delete([env.Dir('opt_x86_32'), + env.Dir('opt_x86_64')])) + + +def AllNaClModules(env, sources, module_name): + '''Add a builder for both the debug and optimized variant of every supported + instruction set architecture. + + Add one builder for each variant of the NaCl module, and also generate the + .nmf that loads the resulting NaCl modules. The .nmf file is named + |module_name|.nmf; similarly all other build products have |module_name| in + their name, e.g. + nacl_env.AllNaClModules(sources, module_name='hello_world') + produces these files, when x86-64 and x86-32 architectures are supported: + hello_world.nmf + hello_world_dbg.nmf + hello_world_x86_32.nexe + hello_world_x86_64.nexe + hello_world_x86_32_dbg.nexe + hello_world_x86_64_dbg.nexe + Object files go in variant directories named 'dbg_*' for debug builds and + 'opt_*' for optimized builds, where the * is a string describing the + architecture, e.g. 'x86_32'. + + Args: + env: Environment to modify. + sources: The list of source files that are used to build the objects. + module_name: The name of the module. + + Returns: + A 2-tuple of SCons Program nodes, the first element is the node that + builds optimized .nexes; the second builds the debug .nexes. + ''' + + opt_nexes = env.NaClModules(sources, module_name, is_debug=False) + env.GenerateNmf(target='%s.nmf' % module_name, + source=opt_nexes, + nexes={'x86-32': '%s_x86_32.nexe' % module_name, + 'x86-64': '%s_x86_64.nexe' % module_name}) + + dbg_nexes = env.NaClModules(sources, module_name, is_debug=True) + env.GenerateNmf(target='%s_dbg.nmf' % module_name, + source=dbg_nexes, + nexes={'x86-32': '%s_x86_32_dbg.nexe' % module_name, + 'x86-64': '%s_x86_64_dbg.nexe' % module_name}) + nacl_utils.PrintNaclPlatformBanner(module_name, + nacl_platform=env['TARGET_NACL_PLATFORM'], + variant=env['NACL_TOOLCHAIN_VARIANT']) + + return opt_nexes, dbg_nexes + + +def AllNaClStaticLibraries(env, sources, lib_name, lib_dir=''): + '''Add a builder for both the debug and optimized variant of every supported + instruction set architecture. + + Add one builder for each variant of the NaCl static library. The library + names have |lib_name| in their name, e.g. + nacl_env.AllNaClModules(sources, lib_name='c_salt') + produces these files, when x86-64 and x86-32 architectures are supported: + libc_salt_x86_32.a + libc_salt_x86_64.a + Object files go in variant directories named 'dbg_*' for debug builds and + 'opt_*' for optimized builds, where the * is a string describing the + architecture, e.g. 'x86_32'. + + Args: + env: Environment to modify. + sources: The list of source files that are used to build the objects. + lib_name: The name of the static library. + lib_dir: Where to output the final library files. Lib files are placed in + directories within lib_dir, based on the arch type and build type. + E.g. Specifying lib_dir = 'dest' for lib name 'c_salt' on x-86-32 debug + creates the library file 'dest/lib-x86-32/dbg/libc_salt.a' + + Returns: + A 2-tuple of SCons StaticLibrary nodes, the first element is the node that + builds optimized libs; the second builds the debug libs. + ''' + + opt_libs = env.NaClStaticLibraries(sources, lib_name, is_debug=False, + lib_dir=lib_dir) + dbg_libs = env.NaClStaticLibraries(sources, lib_name, is_debug=True, + lib_dir=lib_dir) + nacl_utils.PrintNaclPlatformBanner(lib_name, + nacl_platform=env['TARGET_NACL_PLATFORM'], + variant=env['NACL_TOOLCHAIN_VARIANT']) + + return opt_libs, dbg_libs + + +def generate(env): + '''SCons entry point for this tool. + + Args: + env: The SCons Environment to modify. + + NOTE: SCons requires the use of this name, which fails lint. + ''' + nacl_utils.AddCommandLineOptions() + + env.AddMethod(AllNaClModules) + env.AddMethod(AllNaClStaticLibraries) + env.AddMethod(AppendOptCCFlags) + env.AddMethod(AppendArchFlags) + env.AddMethod(FilterOut) + env.AddMethod(InstallPrebuilt) + env.AddMethod(NaClProgram) + env.AddMethod(NaClTestProgram) + env.AddMethod(NaClStaticLib) + env.AddMethod(NaClModules) + env.AddMethod(NaClStaticLibraries) + env.AddMethod(NaClStrippedInstall) + + +def exists(env): + '''The NaCl tool is always valid. This is a required entry point for SCons + Tools. + + Args: + env: The SCons Environment this tool will run in. + + Returns: + Always returns True. + ''' + return True + diff --git a/native_client_sdk/src/build_tools/naclsdk b/native_client_sdk/src/build_tools/naclsdk new file mode 100755 index 0000000..dc48499 --- /dev/null +++ b/native_client_sdk/src/build_tools/naclsdk @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +readonly SCRIPT_DIR="$(dirname "$0")" +readonly SCRIPT_DIR_ABS="$(cd "${SCRIPT_DIR}" ; pwd -P)" +readonly SDK_TOOLS="${SCRIPT_DIR_ABS}/sdk_tools" +readonly SDK_TOOLS_UPDATE="${SCRIPT_DIR_ABS}/sdk_tools_update" + +if [[ -e "${SDK_TOOLS_UPDATE}" ]]; then + echo "Updating sdk_tools" + rm -rf "${SDK_TOOLS}" + mv "${SDK_TOOLS_UPDATE}" "${SDK_TOOLS}" +fi + +export PYTHONPATH="${SDK_TOOLS}" +python "${SDK_TOOLS}/sdk_update.py" "$@" diff --git a/native_client_sdk/src/build_tools/naclsdk.bat b/native_client_sdk/src/build_tools/naclsdk.bat new file mode 100755 index 0000000..ed24701 --- /dev/null +++ b/native_client_sdk/src/build_tools/naclsdk.bat @@ -0,0 +1,24 @@ +@echo off
+
+:: Copyright (c) 2011 The Native Client Authors. All rights reserved.
+:: Use of this source code is governed by a BSD-style license that can be
+:: found in the LICENSE file.
+
+setlocal
+
+set SCRIPT_DIR=%~dp0
+set SDK_TOOLS=%SCRIPT_DIR%sdk_tools
+set SDK_TOOLS_UPDATE=%SCRIPT_DIR%sdk_tools_update
+set PYTHON_DIR=%SCRIPT_DIR%python
+
+if exist %SDK_TOOLS_UPDATE% (
+ echo Updating sdk_tools
+ if exist %SDK_TOOLS% (
+ rmdir /q/s %SDK_TOOLS%
+ )
+ move %SDK_TOOLS_UPDATE% %SDK_TOOLS%
+)
+
+set PYTHONPATH=%SCRIPT_DIR%
+
+python %SDK_TOOLS%\sdk_update.py %*
diff --git a/native_client_sdk/src/build_tools/nsis_script.py b/native_client_sdk/src/build_tools/nsis_script.py new file mode 100644 index 0000000..3ee026f --- /dev/null +++ b/native_client_sdk/src/build_tools/nsis_script.py @@ -0,0 +1,189 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Class that represents an NSIS installer script.""" + +import os +import subprocess +import tarfile +import tempfile + +from build_tools import path_set + + +class Error(Exception): + pass + + +class NsisScript(path_set.PathSet): + '''Container for a NSIS script file + + Use this class to create and manage an NSIS script. You can construct this + class with an existing script file. You can Compile() the script to produce + and NSIS installer. + ''' + + def __init__(self, script_file='nsis.nsi'): + path_set.PathSet.__init__(self) + self._script_file = script_file + self._install_dir = os.path.join('C:%s' % os.sep, 'nsis_install') + self._relative_install_root = None + + @property + def script_file(self): + '''The output NSIS script file. Read-only.''' + return self._script_file + + @property + def install_dir(self): + '''The default directory where the NSIS installer will unpack its contents. + + This must be a full path, including the dirve letter. E.g.: + C:\native_client_sdk + ''' + return self._install_dir + + @install_dir.setter + def install_dir(self, new_install_dir): + if (os.path.isabs(new_install_dir) and + len(os.path.splitdrive(new_install_dir)[0]) > 0): + self._install_dir = new_install_dir + else: + raise Error('install_dir must be an absolute path') + + def InitFromDirectory(self, + artifact_dir, + dir_filter=None, + file_filter=None): + '''Create the list of installer artifacts. + + Creates three lists: + 1. a list of directories that need to be generated by the installer + 2. a list of files that get installed into those directories + 3. a list of symbolic links that need to be created by the installer + These lists are used later on when generating the section commands that are + compiled into the final installer + + Args: + artifact_dir: The directory containing the files to add to the NSIS + installer script. The NSIS installer will reproduce this directory + structure. + + dir_filter: A filter function for directories. This can be written as a + list comprehension. If dir_filter is not None, then it is called + with |_dirs|, and |_dirs| is replaced with the filter's + output. + + file_filter: A filter function for files. This can be written as a + list comprehension. If file_filter is not None, then it is called + with |_files|, and |_files| is replaced with the filter's + output. + ''' + self._relative_install_root = artifact_dir + self.Reset() + for root, dirs, files in os.walk(artifact_dir): + map(lambda dir: self._dirs.add(os.path.join(root, dir)), dirs) + map(lambda file: self._files.add(os.path.join(root, file)), files) + if dir_filter: + self._dirs = set(dir_filter(self._dirs)) + if file_filter: + self._files = set(file_filter(self._files)) + + def CreateInstallNameScript(self, cwd='.'): + '''Write out the installer name script. + + The installer name script is in the special NSIS include file + sdk_install_name.nsh. This file is expected to be in |cwd|. If + sdk_install_name.nsh already exists, it is overwritten. + + Args: + cwd: The directory where sdk_install_name.sdk is placed. + ''' + with open(os.path.join(cwd, 'sdk_install_name.nsh'), 'wb') as script: + script.write('InstallDir %s\n' % self._install_dir) + + def NormalizeInstallPath(self, path): + '''Normalize |path| for installation. + + If |_relative_install_root| is set, then normalize |path| by making it + relative to |_relative_install_root|. + + Args: + path: The path to normalize. + + Returns: the normalized path. If |_relative_install_root| is None, then + the return value is the same as |path|. + ''' + if (self._relative_install_root and + path.startswith(self._relative_install_root)): + return path[len(self._relative_install_root) + 1:] + else: + return path + + def CreateSectionNameScript(self, cwd='.'): + '''Write out the section script. + + The section script is in the special NSIS include file sdk_section.nsh. + This file is expected to be in |cwd|. If sdk_section.nsh already exists, + it is overwritten. + + If |_relative_install_root| is set, then that part of the path is stripped + off of the installed file and directory names. This will cause the files + to be installed as if they were relative to |_relative_install_root| and + not in an absolute location. + + Args: + cwd: The directory where sdk_section.sdk is placed. + ''' + + def SymlinkType(symlink): + '''Return whether the source of symlink is a file or a directory.''' + symlink_basename = os.path.basename(symlink) + for file in self._files: + if os.path.basename(file) == symlink_basename: + return 'SoftF' + return 'SoftD' + + with open(os.path.join(cwd, 'sdk_section.nsh'), 'wb') as script: + script.write('Section "!Native Client SDK" NativeClientSDK\n') + script.write(' SectionIn RO\n') + script.write(' SetOutPath $INSTDIR\n') + for dir in self._dirs: + dir = self.NormalizeInstallPath(dir) + script.write(' CreateDirectory "%s"\n' % os.path.join('$INSTDIR', dir)) + for file in self._files: + file_norm = self.NormalizeInstallPath(file) + script.write(' File "/oname=%s" "%s"\n' % (file_norm, file)) + for src, symlink in self._symlinks.items(): + src_norm = self.NormalizeInstallPath(src) + link_type = SymlinkType(symlink) + script.write(' MkLink::%s "%s" "%s"\n' % ( + link_type, os.path.join('$INSTDIR', src_norm), symlink)) + for src, link in self._links.items(): + src_norm = self.NormalizeInstallPath(src) + link_norm = self.NormalizeInstallPath(link) + script.write(' MkLink::Hard "%s" "%s"\n' % ( + os.path.join('$INSTDIR', src_norm), + os.path.join('$INSTDIR', link_norm))) + script.write('SectionEnd\n') + + def Compile(self): + '''Compile the script. + + Compilation happens in a couple of steps: first, the install directory + script is generated from |_install_dir|, then the section commands script + is generated from |_files|. Finally |_script_file| is compiled, which + produces the NSIS installer specified by the OutFile property in + |_script_file|. + ''' + working_dir = os.path.dirname(self._script_file) + self.CreateInstallNameScript(cwd=working_dir) + self.CreateSectionNameScript(cwd=working_dir) + # Run the NSIS compiler. + subprocess.check_call([os.path.join('NSIS', 'makensis'), + '/V2', + self._script_file], + cwd=working_dir, + shell=True) diff --git a/native_client_sdk/src/build_tools/path_set.py b/native_client_sdk/src/build_tools/path_set.py new file mode 100644 index 0000000..7108ff6 --- /dev/null +++ b/native_client_sdk/src/build_tools/path_set.py @@ -0,0 +1,156 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Class that contains paths classified into four types.""" + +import os + + +class Error(Exception): + pass + + +class PathSet(object): + '''Container for paths classified into four types. + + Paths in this container are broken into four separate collections, each + accessible as a property (see the "Attributes" section, below). + + Attributes: + files: A set of plain files, keys are platform-specific normalized path + names. Might be empty. + dirs: A set of directories, keys are platform-specific normalized path + names. Might be empty. + symlinks: A dictionary of symbolic links - these are not followed. Keys + are platform-specific normalized path names. The value of each key is + the source file of the link (also a platform-specific normalized path). + Might be empty. + links: A dictionary of hard links. Keys are platform-specific normalized + path names. The value of each key is the source file of the link (also + a platform-specific normalized path). Might be empty. + ''' + + def __init__(self): + self.Reset() + + def __ior__(self, other): + '''Override |= to merge |other| into this path set.''' + (self._files, self._dirs, + self._symlinks, self._links) = self._MergeWithPathSet(other) + return self + + def __or__(self, other): + '''Override | to produce the merger of |self| and |other|.''' + merged_path_set = PathSet() + (merged_path_set.files, + merged_path_set.dirs, + merged_path_set.symlinks, + merged_path_set.links) = self._MergeWithPathSet(other) + return merged_path_set + + def _MergeWithPathSet(self, path_set): + '''Merge this path set with |path_set|. + + Forms the union of the |_files| and |_dirs| sets, then merges the + |_symlinks| and |_links| dicts. The dictionaries are merged in such that + keys are not duplicated: the values of the keys in |path_set| take + precedence in the returned dictionaries. + + Any keys in either the symlink or links dictionaries that also exist in + either of the files or dicts sets are removed from the latter, meaning that + symlinks or links which overlap file or directory entries take precedence. + + Args: + path_set: The other path set. Must be an object with four properties: + files (a set), dirs (a set), symlinks (a dict), links (a dict). + Returns: + A four-tuple (files, dirs, symlinks, links) which is the result of merging + the two PathSets. + ''' + def DiscardOverlappingLinks(links_dict): + '''Discard all overlapping keys from files and dirs.''' + for link in links_dict: + self._files.discard(link) + self._dirs.discard(link) + + DiscardOverlappingLinks(path_set.symlinks) + DiscardOverlappingLinks(path_set.links) + return (self._files | path_set.files, + self._dirs | path_set.dirs, + dict(self._symlinks.items() + path_set.symlinks.items()), + dict(self._links.items() + path_set.links.items())) + + @property + def files(self): + '''The set of plain files.''' + return self._files + + @files.setter + def files(self, new_file_set): + if isinstance(new_file_set, set): + self._files = new_file_set + else: + raise Error('files property must be a set') + + @property + def dirs(self): + '''The set of directories.''' + return self._dirs + + @dirs.setter + def dirs(self, new_dir_set): + if isinstance(new_dir_set, set): + self._dirs = new_dir_set + else: + raise Error('dirs property must be a set') + + @property + def symlinks(self): + '''The dictionary of symbolic links.''' + return self._symlinks + + @symlinks.setter + def symlinks(self, new_symlinks_dict): + if isinstance(new_symlinks_dict, dict): + self._symlinks = new_symlinks_dict + else: + raise Error('symlinks property must be a dict') + + @property + def links(self): + '''The dictionary of hard links.''' + return self._links + + @links.setter + def links(self, new_links_dict): + if isinstance(new_links_dict, dict): + self._links = new_links_dict + else: + raise Error('links property must be a dict') + + def Reset(self): + '''Reset all attributes to empty containers.''' + self._files = set() + self._dirs = set() + self._symlinks = dict() + self._links = dict() + + def PrependPath(self, path_prefix): + '''Prepend paths in all collections with |path_prefix|. + + All the keys in all the colletions get prepended with |path_prefix|. The + resulting path is an os-specific path. + + Args: + path_prefix: The path to prepend to all collection keys. + ''' + prepend_path = lambda p: os.path.join(path_prefix, p) + def PrependToLinkDict(link_dict): + return dict([prepend_path(p), link] for p, link in link_dict.items()) + + self._files = set(map(prepend_path, self._files)) + self._dirs = set(map(prepend_path, self._dirs)) + self._symlinks = PrependToLinkDict(self._symlinks) + self._links = PrependToLinkDict(self._links) diff --git a/native_client_sdk/src/build_tools/sdk_tools/__init__.py b/native_client_sdk/src/build_tools/sdk_tools/__init__.py new file mode 100644 index 0000000..15d26bf --- /dev/null +++ b/native_client_sdk/src/build_tools/sdk_tools/__init__.py @@ -0,0 +1,7 @@ +#! -*- python -*- + +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""sdk_tools Package""" diff --git a/native_client_sdk/src/build_tools/sdk_tools/sdk_update.py b/native_client_sdk/src/build_tools/sdk_tools/sdk_update.py new file mode 100755 index 0000000..d5c6f25 --- /dev/null +++ b/native_client_sdk/src/build_tools/sdk_tools/sdk_update.py @@ -0,0 +1,1039 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +'''A simple tool to update the Native Client SDK to the latest version''' + +import cStringIO +import errno +import exceptions +import hashlib +import json +import optparse +import os +import shutil +import subprocess +import sys +import tarfile +import tempfile +import time +import urllib2 +import urlparse + + +#------------------------------------------------------------------------------ +# Constants + +# Bump the MINOR_REV every time you check this file in. +MAJOR_REV = 1 +MINOR_REV = 12 + +GLOBAL_HELP = '''Usage: naclsdk [options] command [command_options] + +naclsdk is a simple utility that updates the Native Client (NaCl) +Software Developer's Kit (SDK). Each component is kept as a 'bundle' that +this utility can download as as subdirectory into the SDK. + +Commands: + help [command] - Get either general or command-specific help + list - Lists the available bundles + update/install - Updates/installs bundles in the SDK + +Example Usage: + naclsdk list + naclsdk update --force pepper_17 + naclsdk install recommended + naclsdk help update''' + +MANIFEST_FILENAME='naclsdk_manifest.json' +SDK_TOOLS='sdk_tools' # the name for this tools directory +USER_DATA_DIR='sdk_cache' + +HTTP_CONTENT_LENGTH = 'Content-Length' # HTTP Header field for content length + +# The following SSL certificates are used to validate the SSL connection +# to https://commondatastorage.googleapis.com +# TODO(mball): Validate at least one of these certificates. +# http://stackoverflow.com/questions/1087227/validate-ssl-certificates-with-python + +EQUIFAX_SECURE_CA_CERTIFICATE='''-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV +UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy +dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 +MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx +dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f +BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A +cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC +AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ +MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw +ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj +IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF +MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA +A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y +7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh +1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 +-----END CERTIFICATE-----''' + +GOOGLE_INTERNET_AUTHORITY_CERTIFICATE='''-----BEGIN CERTIFICATE----- +MIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT +MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0 +aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3 +WjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ +R29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf +NFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb +qeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB +oDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIk +MB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYB +Af8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v +Y3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHde +BZqrocb6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN +0rTw8Ktx5YtaiScRhKqOv5nwnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfml +UUIuOss4jHg7y/j7lYe8vJD5UDI= +-----END CERTIFICATE-----''' + +GOOGLE_USER_CONTENT_CERTIFICATE='''-----BEGIN CERTIFICATE----- +MIIEPDCCA6WgAwIBAgIKUaoA4wADAAAueTANBgkqhkiG9w0BAQUFADBGMQswCQYD +VQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu +dGVybmV0IEF1dGhvcml0eTAeFw0xMTA4MTIwMzQ5MjlaFw0xMjA4MTIwMzU5Mjla +MHExCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N +b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMSAwHgYDVQQDFBcqLmdv +b2dsZXVzZXJjb250ZW50LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA +uDmDvqlKBj6DppbENEuUmwVsHe5hpixV0bn6D+Ujy3mWUP9HtkO35/RmeFf4/y9i +nGy78uWO6tk9QY1PsPSiyZN6LgplalBdkTeODCGAieVOVJFhHQ0KM330qDy9sKNM +rwdMOfLPzkBMYPyr1C7CCm24j//aFiMCxD40bDQXRJkCAwEAAaOCAgQwggIAMB0G +A1UdDgQWBBRyHxPfv+Lnm2Kgid72ja3pOszMsjAfBgNVHSMEGDAWgBS/wDDr9UMR +Pme6npH7/Gra42sSJDBbBgNVHR8EVDBSMFCgTqBMhkpodHRwOi8vd3d3LmdzdGF0 +aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9yaXR5L0dvb2dsZUludGVybmV0QXV0 +aG9yaXR5LmNybDBmBggrBgEFBQcBAQRaMFgwVgYIKwYBBQUHMAKGSmh0dHA6Ly93 +d3cuZ3N0YXRpYy5jb20vR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkvR29vZ2xlSW50 +ZXJuZXRBdXRob3JpdHkuY3J0MCEGCSsGAQQBgjcUAgQUHhIAVwBlAGIAUwBlAHIA +dgBlAHIwgdUGA1UdEQSBzTCByoIXKi5nb29nbGV1c2VyY29udGVudC5jb22CFWdv +b2dsZXVzZXJjb250ZW50LmNvbYIiKi5jb21tb25kYXRhc3RvcmFnZS5nb29nbGVh +cGlzLmNvbYIgY29tbW9uZGF0YXN0b3JhZ2UuZ29vZ2xlYXBpcy5jb22CEGF0Z2ds +c3RvcmFnZS5jb22CEiouYXRnZ2xzdG9yYWdlLmNvbYIUKi5zLmF0Z2dsc3RvcmFn +ZS5jb22CCyouZ2dwaHQuY29tgglnZ3BodC5jb20wDQYJKoZIhvcNAQEFBQADgYEA +XDvIl0/id823eokdFpLA8bL3pb7wQGaH0i3b29572aM7cDKqyxmTBbwi9mMMgbxy +E/St8DoSEQg3cJ/t2UaTXtw8wCrA6M1dS/RFpNLfV84QNcVdNhLmKEuZjpa+miUK +8OtYzFSMdfwXrbqKgkAIaqUs6m+LWKG/AQShp6DvTPo= +-----END CERTIFICATE-----''' + +# Some commonly-used key names. +ARCHIVES_KEY = 'archives' +BUNDLES_KEY = 'bundles' +NAME_KEY = 'name' +REVISION_KEY = 'revision' +VERSION_KEY = 'version' + +# Valid values for bundle.stability field +STABILITY_LITERALS = [ + 'obsolete', 'post_stable', 'stable', 'beta', 'dev', 'canary'] +# Valid values for the archive.host_os field +HOST_OS_LITERALS = frozenset(['mac', 'win', 'linux', 'all']) +# Valid values for bundle-recommended field. +YES_NO_LITERALS = ['yes', 'no'] +# Valid keys for various sdk objects, used for validation. +VALID_ARCHIVE_KEYS = frozenset(['host_os', 'size', 'checksum', 'url']) +VALID_BUNDLES_KEYS = frozenset([ + ARCHIVES_KEY, NAME_KEY, VERSION_KEY, REVISION_KEY, + 'description', 'desc_url', 'stability', 'recommended', + ]) +VALID_MANIFEST_KEYS = frozenset(['manifest_version', BUNDLES_KEY]) + + +#------------------------------------------------------------------------------ +# General Utilities + + +_debug_mode = False +_quiet_mode = False + + +def DebugPrint(msg): + '''Display a message to stderr if debug printing is enabled + + Note: This function appends a newline to the end of the string + + Args: + msg: A string to send to stderr in debug mode''' + if _debug_mode: + sys.stderr.write("%s\n" % msg) + sys.stderr.flush() + + +def InfoPrint(msg): + '''Display an informational message to stdout if not in quiet mode + + Note: This function appends a newline to the end of the string + + Args: + mgs: A string to send to stdio when not in quiet mode''' + if not _quiet_mode: + sys.stdout.write("%s\n" % msg) + sys.stdout.flush() + + +def WarningPrint(msg): + '''Display an informational message to stderr. + + Note: This function appends a newline to the end of the string + + Args: + mgs: A string to send to stderr.''' + sys.stderr.write("WARNING: %s\n" % msg) + sys.stderr.flush() + + +class Error(Exception): + '''Generic error/exception for sdk_update module''' + pass + + +def GetHostOS(): + '''Returns the host_os value that corresponds to the current host OS''' + return { + 'linux2': 'linux', + 'darwin': 'mac', + 'cygwin': 'win', + 'win32': 'win' + }[sys.platform] + + +def ExtractInstaller(installer, outdir): + '''Extract the SDK installer into a given directory + + If the outdir already exists, then this function deletes it + + Args: + installer: full path of the SDK installer + outdir: output directory where to extract the installer + + Raises: + CalledProcessError - if the extract operation fails''' + if os.path.exists(outdir): + RemoveDir(outdir) + + if os.path.splitext(installer)[1] == '.exe': + # If the installer has extension 'exe', assume it's a Windows NSIS-style + # installer that handles silent (/S) and relocated (/D) installs. + command = [installer, '/S', '/D=%s' % outdir] + subprocess.check_call(command) + else: + os.mkdir(outdir) + tar_file = None + try: + tar_file = tarfile.open(installer) + tar_file.extractall(path=outdir) + finally: + if tar_file: + tar_file.close() + + +def RemoveDir(outdir): + '''Removes the given directory + + On Unix systems, this just runs shutil.rmtree, but on Windows, this doesn't + work when the directory contains junctions (as does our SDK installer). + Therefore, on Windows, it runs rmdir /S /Q as a shell command. This always + does the right thing on Windows. + + Args: + outdir: The directory to delete + + Raises: + CalledProcessError - if the delete operation fails on Windows + OSError - if the delete operation fails on Linux + ''' + + DebugPrint('Removing %s' % outdir) + if sys.platform == 'win32': + subprocess.check_call(['rmdir /S /Q', outdir], shell=True) + else: + shutil.rmtree(outdir) + + +def RenameDir(srcdir, destdir): + '''Renames srcdir to destdir. Removes destdir before doing the + rename if it already exists.''' + + max_tries = 100 + + for num_tries in xrange(max_tries): + try: + if os.path.exists(destdir): + RemoveDir(destdir) + os.rename(srcdir, destdir) + return + except OSError as err: + if err.errno != errno.EACCES: + raise err + # If we are here, we didn't exit due to raised exception, so we are + # handling a Windows flaky access error. Sleep one second and try + # again. + time.sleep(1) + # end of while loop -- could not RenameDir + raise Error('Could not RenameDir %s => %s after %d tries.\n' % + 'Please check that no shells or applications ' + 'are accessing files in %s.' + % (srcdir, destdir, num_tries, destdir)) + + +def ShowProgress(progress): + ''' A download-progress function used by class Archive. + (See DownloadAndComputeHash).''' + global count # A divider, so we don't emit dots too often. + + if progress == 0: + count = 0 + elif progress == 100: + sys.stdout.write('\n') + else: + count = count + 1 + if count > 10: + sys.stdout.write('.') + sys.stdout.flush() + count = 0 + + +class ProgressFunction(object): + '''Create a progress function for a file with a given size''' + + def __init__(self, file_size=0): + '''Constructor + + Args: + file_size: number of bytes in file. 0 indicates unknown''' + self.dots = 0 + self.file_size = int(file_size) + + def GetProgressFunction(self): + '''Returns a progress function based on a known file size''' + def ShowKnownProgress(progress): + if progress == 0: + sys.stdout.write('|%s|\n' % ('=' * 48)) + else: + new_dots = progress * 50 / self.file_size - self.dots + sys.stdout.write('.' * new_dots) + self.dots += new_dots + if progress == self.file_size: + sys.stdout.write('\n') + sys.stdout.flush() + + return ShowKnownProgress + + +def DownloadAndComputeHash(from_stream, to_stream=None, progress_func=None): + ''' Download the archive data from from-stream and generate sha1 and + size info. + + Args: + from_stream: An input stream that supports read. + to_stream: [optional] the data is written to to_stream if it is + provided. + progress_func: [optional] A function used to report download progress. If + provided, progress_func is called with progress=0 at the + beginning of the download, periodically with progress=1 + during the download, and progress=100 at the end. + + Return + A tuple (sha1, size) where sha1 is a sha1-hash for the archive data and + size is the size of the archive data in bytes.''' + # Use a no-op progress function if none is specified. + def progress_no_op(progress): + pass + if not progress_func: + progress_func = progress_no_op + + sha1_hash = hashlib.sha1() + size = 0 + progress_func(progress=0) + while(1): + data = from_stream.read(32768) + if not data: + break + sha1_hash.update(data) + size += len(data) + if to_stream: + to_stream.write(data) + progress_func(size) + + progress_func(progress=100) + return sha1_hash.hexdigest(), size + + +class Archive(dict): + ''' A placeholder for sdk archive information. We derive Archive from + dict so that it is easily serializable. ''' + def __init__(self, host_os_name): + ''' Create a new archive for the given host-os name. ''' + self['host_os'] = host_os_name + + def CopyFrom(self, dict): + ''' Update the content of the archive by copying values from the given + dictionary. + + Args: + dict: The dictionary whose values must be copied to the archive.''' + for key, value in dict.items(): + self[key] = value + + def Validate(self): + ''' Validate the content of the archive object. Raise an Error if + an invalid or missing field is found. + Returns: True if self is a valid bundle. + ''' + host_os = self.get('host_os', None) + if host_os and host_os not in HOST_OS_LITERALS: + raise Error('Invalid host-os name in archive') + # Ensure host_os has a valid string. We'll use it for pretty printing. + if not host_os: + host_os = 'all (default)' + if not self.get('url', None): + raise Error('Archive "%s" has no URL' % host_os) + # Verify that all key names are valid. + for key, val in self.iteritems(): + if key not in VALID_ARCHIVE_KEYS: + raise Error('Archive "%s" has invalid attribute "%s"' % (host_os, key)) + + def _OpenURLStream(self): + ''' Open a file-like stream for the archives's url. Raises an Error if the + url can't be opened. + + Return: + A file-like object from which the archive's data can be read.''' + try: + url_stream = urllib2.urlopen(self['url']) + except urllib2.URLError: + raise Error('Cannot open "%s" for archive %s' % + (self['url'], self['host_os'])) + + return url_stream + + def ComputeSha1AndSize(self): + ''' Compute the sha1 hash and size of the archive's data. Raises + an Error if the url can't be opened. + + Return: + A tuple (sha1, size) with the sha1 hash and data size respectively.''' + stream = None + sha1 = None + size = 0 + try: + print 'Scanning archive to generate sha1 and size info:' + stream = self._OpenURLStream() + content_length = int(stream.info()[HTTP_CONTENT_LENGTH]) + sha1, size = DownloadAndComputeHash(from_stream=stream, + progress_func=ShowProgress) + if size != content_length: + raise Error('Download size mismatch for %s.\n' + 'Expected %s bytes but got %s' % + (self['url'], content_length, size)) + finally: + if stream: stream.close() + return sha1, size + + def DownloadToFile(self, dest_path): + ''' Download the archive's data to a file at dest_path. As a side effect, + computes the sha1 hash and data size, both returned as a tuple. Raises + an Error if the url can't be opened, or an IOError exception if + dest_path can't be opened. + + Args: + dest_path: Path for the file that will receive the data. + Return: + A tuple (sha1, size) with the sha1 hash and data size respectively.''' + sha1 = None + size = 0 + with open(dest_path, 'wb') as to_stream: + from_stream = None + try: + from_stream = self._OpenURLStream() + content_length = int(from_stream.info()[HTTP_CONTENT_LENGTH]) + progress_function = ProgressFunction( + content_length).GetProgressFunction() + InfoPrint('Downloading %s' % self['url']) + sha1, size = DownloadAndComputeHash( + from_stream, + to_stream=to_stream, + progress_func=progress_function) + if size != content_length: + raise Error('Download size mismatch for %s.\n' + 'Expected %s bytes but got %s' % + (self['url'], content_length, size)) + finally: + if from_stream: from_stream.close() + return sha1, size + + def Update(self, url): + ''' Update the archive with the new url. Automatically update the + archive's size and checksum fields. Raises an Error if the url is + is invalid. ''' + self['url'] = url + sha1, size = self.ComputeSha1AndSize() + self['size'] = size + self['checksum'] = {'sha1': sha1} + + +class Bundle(dict): + ''' A placeholder for sdk bundle information. We derive Bundle from + dict so that it is easily serializable.''' + def __init__(self, obj): + ''' Create a new bundle with the given bundle name.''' + if isinstance(obj, str) or isinstance(obj, unicode): + dict.__init__(self, [(ARCHIVES_KEY, []), (NAME_KEY, obj)]) + else: + dict.__init__(self, obj) + + def MergeWithBundle(self, bundle): + '''Merge this bundle with |bundle|. + + Merges dict in |bundle| with this one in such a way that keys are not + duplicated: the values of the keys in |bundle| take precedence in the + returned dictionary. + + Any keys in either the symlink or links dictionaries that also exist in + either of the files or dicts sets are removed from the latter, meaning that + symlinks or links which overlap file or directory entries take precedence. + + Args: + bundle: The other bundle. Must be a dict. + Returns: + A dict which is the result of merging the two Bundles. + ''' + return Bundle(self.items() + bundle.items()) + + def CopyFrom(self, dict): + ''' Update the content of the bundle by copying values from the given + dictionary. + + Args: + dict: The dictionary whose values must be copied to the bundle.''' + for key, value in dict.items(): + if key == ARCHIVES_KEY: + archives = [] + for a in value: + new_archive = Archive(a['host_os']) + new_archive.CopyFrom(a) + archives.append(new_archive) + self[ARCHIVES_KEY] = archives + else: + self[key] = value + + def Validate(self): + ''' Validate the content of the bundle. Raise an Error if an invalid or + missing field is found. ''' + # Check required fields. + if not self.get(NAME_KEY, None): + raise Error('Bundle has no name') + if self.get(REVISION_KEY, None) == None: + raise Error('Bundle "%s" is missing a revision number' % self[NAME_KEY]) + if self.get(VERSION_KEY, None) == None: + raise Error('Bundle "%s" is missing a version number' % self[NAME_KEY]) + if not self.get('description', None): + raise Error('Bundle "%s" is missing a description' % self[NAME_KEY]) + if not self.get('stability', None): + raise Error('Bundle "%s" is missing stability info' % self[NAME_KEY]) + if self.get('recommended', None) == None: + raise Error('Bundle "%s" is missing the recommended field' % + self[NAME_KEY]) + # Check specific values + if self['stability'] not in STABILITY_LITERALS: + raise Error('Bundle "%s" has invalid stability field: "%s"' % + (self[NAME_KEY], self['stability'])) + if self['recommended'] not in YES_NO_LITERALS: + raise Error( + 'Bundle "%s" has invalid recommended field: "%s"' % + (self[NAME_KEY], self['recommended'])) + # Verify that all key names are valid. + for key, val in self.iteritems(): + if key not in VALID_BUNDLES_KEYS: + raise Error('Bundle "%s" has invalid attribute "%s"' % + (self[NAME_KEY], key)) + # Validate the archives + for archive in self[ARCHIVES_KEY]: + archive.Validate() + + def GetArchive(self, host_os_name): + ''' Retrieve the archive for the given host os. + + Args: + host_os_name: name of host os whose archive must be retrieved. + Return: + An Archive instance or None if it doesn't exist.''' + for archive in self[ARCHIVES_KEY]: + if archive['host_os'] == host_os_name: + return archive + return None + + def UpdateArchive(self, host_os, url): + ''' Update or create the archive for host_os with the new url. + Automatically updates the archive size and checksum info by downloading + the data from the given archive. Raises an Error if the url is invalid. + + Args: + host_os: name of host os whose archive must be updated or created. + url: the new url for the archive.''' + archive = self.GetArchive(host_os) + if not archive: + archive = Archive(host_os_name=host_os) + self[ARCHIVES_KEY].append(archive) + archive.Update(url) + + +class SDKManifest(object): + '''This class contains utilities for manipulation an SDK manifest string + + For ease of unit-testing, this class should not contain any file I/O. + ''' + + def __init__(self): + '''Create a new SDKManifest object with default contents''' + self.MANIFEST_VERSION = 1 + self._manifest_data = { + "manifest_version": self.MANIFEST_VERSION, + "bundles": [], + } + + def _ValidateManifest(self): + '''Validate the Manifest file and raises an exception for problems''' + # Validate the manifest top level + if self._manifest_data["manifest_version"] > self.MANIFEST_VERSION: + raise Error("Manifest version too high: %s" % + self._manifest_data["manifest_version"]) + # Verify that all key names are valid. + for key, val in self._manifest_data.iteritems(): + if key not in VALID_MANIFEST_KEYS: + raise Error('Manifest has invalid attribute "%s"' % key) + # Validate each bundle + for bundle in self._manifest_data[BUNDLES_KEY]: + bundle.Validate() + + def GetBundle(self, name): + ''' Get a bundle from the array of bundles. + + Args: + name: the name of the bundle to return. + Return: + The first bundle with the given name, or None if it is not found.''' + if not BUNDLES_KEY in self._manifest_data: + return None + bundles = filter(lambda b: b[NAME_KEY] == name, + self._manifest_data[BUNDLES_KEY]) + if len(bundles) > 1: + WarningPrint("More than one bundle with name '%s' exists." % name) + return bundles[0] if len(bundles) > 0 else None + + def SetBundle(self, new_bundle): + '''Replace named bundle. Add if absent. + + Args: + name: Name of the bundle to replace or add. + bundle: The bundle. + ''' + name = new_bundle[NAME_KEY] + if not BUNDLES_KEY in self._manifest_data: + self._manifest_data[BUNDLES_KEY] = [] + bundles = self._manifest_data[BUNDLES_KEY] + # Delete any bundles from the list, then add the new one. This has the + # effect of replacing the bundle if it already exists. It also removes all + # duplicate bundles. + for i, bundle in enumerate(bundles): + if bundle[NAME_KEY] == name: + del bundles[i] + bundles.append(new_bundle) + + def LoadManifestString(self, json_string, all_hosts=False): + ''' Load a JSON manifest string. Raises an exception if json_string + is not well-formed JSON. + + Args: + json_string: a JSON-formatted string containing the previous manifest + all_hosts: True indicates that we should load bundles for all hosts. + False (default) says to only load bundles for the current host''' + new_manifest = json.loads(json_string) + for key, value in new_manifest.items(): + if key == BUNDLES_KEY: + # Remap each bundle in |value| to a Bundle instance + bundles = [] + for b in value: + new_bundle = Bundle(b[NAME_KEY]) + new_bundle.CopyFrom(b) + # Only add this archive if it's supported on this platform. + # However, the sdk_tools bundle might not have an archive entry, + # but is still always valid. + if (all_hosts or new_bundle.GetArchive(GetHostOS()) or + b[NAME_KEY] == 'sdk_tools'): + bundles.append(new_bundle) + self._manifest_data[key] = bundles + else: + self._manifest_data[key] = value + self._ValidateManifest() + + def GetManifestString(self): + '''Returns the current JSON manifest object, pretty-printed''' + pretty_string = json.dumps(self._manifest_data, sort_keys=False, indent=2) + # json.dumps sometimes returns trailing whitespace and does not put + # a newline at the end. This code fixes these problems. + pretty_lines = pretty_string.split('\n') + return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n' + + +class SDKManifestFile(object): + ''' This class provides basic file I/O support for manifest objects.''' + + def __init__(self, json_filepath): + '''Create a new SDKManifest object with default contents. + + If |json_filepath| is specified, and it exists, its contents are loaded and + used to initialize the internal manifest. + + Args: + json_filepath: path to jason file to read/write, or None to write a new + manifest file to stdout. + ''' + self._json_filepath = json_filepath + self._manifest = SDKManifest() + if self._json_filepath: + self._LoadFile() + + def _LoadFile(self): + '''Load the manifest from the JSON file. + + This function returns quietly if the file doesn't exit. + ''' + if not os.path.exists(self._json_filepath): + return + + with open(self._json_filepath, 'r') as f: + json_string = f.read() + if json_string: + self._manifest.LoadManifestString(json_string, all_hosts=True) + + def WriteFile(self): + '''Write the json data to the file. If not file name was specified, the + data is written to stdout.''' + json_string = self._manifest.GetManifestString() + if not self._json_filepath: + # No file is specified; print the json data to stdout + sys.stdout.write(json_string) + else: + # Write the JSON data to a temp file. + temp_file_name = None + # TODO(dspringer): Use file locks here so that multiple sdk_updates can + # run at the same time. + with tempfile.NamedTemporaryFile(mode='w', delete=False) as f: + f.write(json_string) + temp_file_name = f.name + # Move the temp file to the actual file. + if os.path.exists(self._json_filepath): + os.remove(self._json_filepath) + shutil.move(temp_file_name, self._json_filepath) + + def GetBundles(self): + '''Return all the bundles in |_manifest|''' + return self._manifest._manifest_data[BUNDLES_KEY] + + def GetBundleNamed(self, name): + '''Return the first bundle named |name| or None if it doesn't exist''' + return self._manifest.GetBundle(name) + + def BundleNeedsUpdate(self, bundle): + '''Decides if a bundle needs to be updated. + + A bundle needs to be updated if it is not installed (doesn't exist in this + manifest file) or if its revision is later than the revision in this file. + + Args: + bundle: The Bundle to test. + Returns: + True if Bundle needs to be updated. + ''' + if NAME_KEY not in bundle: + raise KeyError("Bundle must have a 'name' key.") + local_bundle = self.GetBundleNamed(bundle[NAME_KEY]) + return (local_bundle == None) or ( + (local_bundle[VERSION_KEY], local_bundle[REVISION_KEY]) < + (bundle[VERSION_KEY], bundle[REVISION_KEY])) + + def MergeBundle(self, bundle): + '''Merge a Bundle into this manifest. + + The new bundle is added if not present, or merged into the existing bundle. + + Args: + bundle: The bundle to merge. + ''' + if NAME_KEY not in bundle: + raise KeyError("Bundle must have a 'name' key.") + local_bundle = self.GetBundleNamed(bundle[NAME_KEY]) + if not local_bundle: + self._manifest.SetBundle(bundle) + else: + self._manifest.SetBundle(local_bundle.MergeWithBundle(bundle)) + + +class ManifestTools(object): + '''Wrapper class for supporting the SDK manifest file''' + + def __init__(self, options): + self._options = options + self._manifest = SDKManifest() + + def LoadManifest(self): + DebugPrint("Running LoadManifest") + try: + # TODO(mball): Add certificate validation on the server + url_stream = urllib2.urlopen(self._options.manifest_url) + except urllib2.URLError: + raise Error('Unable to open %s' % self._options.manifest_url) + + manifest_stream = cStringIO.StringIO() + sha1, size = DownloadAndComputeHash( + url_stream, manifest_stream) + self._manifest.LoadManifestString(manifest_stream.getvalue()) + + def GetBundles(self): + return self._manifest._manifest_data[BUNDLES_KEY] + + +#------------------------------------------------------------------------------ +# Commands + + +def List(options, argv): + '''Usage: %prog [options] list + + Lists the available SDK bundles that are available for download.''' + def PrintBundles(bundles): + for bundle in bundles: + InfoPrint(' %s' % bundle[NAME_KEY]) + for key, value in bundle.iteritems(): + if key not in [ARCHIVES_KEY, NAME_KEY]: + InfoPrint(' %s: %s' % (key, value)) + + DebugPrint("Running List command with: %s, %s" %(options, argv)) + + parser = optparse.OptionParser(usage=List.__doc__) + (list_options, args) = parser.parse_args(argv) + tools = ManifestTools(options) + tools.LoadManifest() + bundles = tools.GetBundles() + InfoPrint('Available bundles:') + PrintBundles(bundles) + # Print the local information. + local_manifest = SDKManifestFile(os.path.join(options.user_data_dir, + options.manifest_filename)) + bundles = local_manifest.GetBundles() + InfoPrint('\nCurrently installed bundles:') + PrintBundles(bundles) + + +def Update(options, argv): + '''Usage: %prog [options] update [target] + + Updates the Native Client SDK to a specified version. By default, this + command updates all the recommended components. The update process works + like this: + 1. Fetch the manifest from the mirror. + 2. Load manifest from USER_DATA_DIR - if there is no local manifest file, + make an empty manifest object. + 3. Update each the bundle: + for bundle in bundles: + # Compare bundle versions & revisions. + # Test if local version.revision < mirror OR local doesn't exist. + if local_manifest < mirror_manifest: + update(bundle) + update local_manifest with mirror_manifest for bundle + write manifest to disk. Use locks. + else: + InfoPrint('bundle is up-to-date') + + Targets: + recommended: (default) Install/Update all recommended components + all: Install/Update all available components + bundle_name: Install/Update only the given bundle + ''' + DebugPrint("Running Update command with: %s, %s" % (options, argv)) + ALL='all' # Update all bundles + RECOMMENDED='recommended' # Only update the bundles with recommended=yes + + parser = optparse.OptionParser(usage=Update.__doc__) + parser.add_option( + '-F', '--force', dest='force', + default=False, action='store_true', + help='Force updating existing components that already exist') + (update_options, args) = parser.parse_args(argv) + if len(args) == 0: + args = [RECOMMENDED] + tools = ManifestTools(options) + tools.LoadManifest() + bundles = tools.GetBundles() + local_manifest = SDKManifestFile(os.path.join(options.user_data_dir, + options.manifest_filename)) + for bundle in bundles: + bundle_name = bundle[NAME_KEY] + bundle_path = os.path.join(options.sdk_root_dir, bundle_name) + bundle_update_path = '%s_update' % bundle_path + if not (bundle_name in args or + ALL in args or (RECOMMENDED in args and + bundle[RECOMMENDED] == 'yes')): + continue + def UpdateBundle(): + '''Helper to install a bundle''' + archive = bundle.GetArchive(GetHostOS()) + (scheme, host, path, _, _, _) = urlparse.urlparse(archive['url']) + dest_filename = os.path.join(options.user_data_dir, path.split('/')[-1]) + sha1, size = archive.DownloadToFile(os.path.join(options.user_data_dir, + dest_filename)) + if sha1 != archive['checksum']['sha1']: + raise Error("SHA1 checksum mismatch on '%s'. Expected %s but got %s" % + (bundle_name, archive['checksum']['sha1'], sha1)) + if size != archive['size']: + raise Error("Size mismatch on Archive. Expected %s but got %s bytes" % + (archive['size'], size)) + InfoPrint('Updating bundle %s to version %s, revision %s' % ( + (bundle_name, bundle[VERSION_KEY], bundle[REVISION_KEY]))) + ExtractInstaller(dest_filename, bundle_update_path) + if bundle_name != SDK_TOOLS: + RenameDir(bundle_update_path, bundle_path) + os.remove(dest_filename) + local_manifest.MergeBundle(bundle) + local_manifest.WriteFile() + # Test revision numbers, update the bundle accordingly. + # TODO(dspringer): The local file should be refreshed from disk each + # iteration thought this loop so that multiple sdk_updates can run at the + # same time. + if local_manifest.BundleNeedsUpdate(bundle): + if (not update_options.force and os.path.exists(bundle_path) and + bundle_name != SDK_TOOLS): + WarningPrint('%s already exists, but has an update available.\n' + 'Run update with the --force option to overwrite the ' + 'existing directory.\nWarning: This will overwrite any ' + 'modifications you have made within this directory.' + % bundle_name) + else: + UpdateBundle() + else: + InfoPrint('%s is already up-to-date.' % bundle_name) + + # Validate the arg list against the available bundle names. Raises an + # error if any invalid bundle names or args are detected. + valid_args = set([ALL, RECOMMENDED] + + [bundle[NAME_KEY] for bundle in bundles]) + bad_args = set(args) - valid_args + if len(bad_args) > 0: + raise Error("Unrecognized bundle name or argument: '%s'" % + ', '.join(bad_args)) + + +#------------------------------------------------------------------------------ +# Command-line interface + + +def main(argv): + '''Main entry for the sdk_update utility''' + parser = optparse.OptionParser(usage=GLOBAL_HELP) + DEFAULT_SDK_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + parser.add_option( + '-U', '--manifest-url', dest='manifest_url', + default='https://commondatastorage.googleapis.com/nativeclient-mirror/' + 'nacl/nacl_sdk/%s' % MANIFEST_FILENAME, + help='override the default URL for the NaCl manifest file') + parser.add_option( + '-d', '--debug', dest='debug', + default=False, action='store_true', + help='enable displaying debug information to stderr') + parser.add_option( + '-q', '--quiet', dest='quiet', + default=False, action='store_true', + help='suppress displaying informational prints to stdout') + parser.add_option( + '-u', '--user-data-dir', dest='user_data_dir', + # TODO(mball): the default should probably be in something like + # ~/.naclsdk (linux), or ~/Library/Application Support/NaClSDK (mac), + # or %HOMEPATH%\Application Data\NaClSDK (i.e., %APPDATA% on windows) + default=os.path.join(DEFAULT_SDK_ROOT, USER_DATA_DIR), + help="specify location of NaCl SDK's data directory") + parser.add_option( + '-s', '--sdk-root-dir', dest='sdk_root_dir', + default=DEFAULT_SDK_ROOT, + help="location where the SDK bundles are installed") + parser.add_option( + '-v', '--version', dest='show_version', + action='store_true', + help='show version information and exit') + parser.add_option( + '-m', '--manifest', dest='manifest_filename', + default=MANIFEST_FILENAME, + help="name of local manifest file relative to user-data-dir") + + COMMANDS = { + 'list': List, + 'update': Update, + 'install': Update, + } + + # Separate global options from command-specific options + global_argv = argv + command_argv = [] + for index, arg in enumerate(argv): + if arg in COMMANDS: + global_argv = argv[:index] + command_argv = argv[index:] + break + + (options, args) = parser.parse_args(global_argv) + args += command_argv + + global _debug_mode, _quiet_mode + _debug_mode = options.debug + _quiet_mode = options.quiet + + def PrintHelpAndExit(unused_options=None, unused_args=None): + parser.print_help() + exit(1) + + if options.show_version: + print "Native Client SDK Updater, version %s.%s" % (MAJOR_REV, MINOR_REV) + exit(0) + + if not args: + print "Need to supply a command" + PrintHelpAndExit() + + def DefaultHandler(unused_options=None, unused_args=None): + print "Unknown Command: %s" % args[0] + PrintHelpAndExit() + + def InvokeCommand(args): + command = COMMANDS.get(args[0], DefaultHandler) + command(options, args[1:]) + + if args[0] == 'help': + if len(args) == 1: + PrintHelpAndExit() + else: + InvokeCommand([args[1], '-h']) + else: + # Make sure the user_data_dir exists. + if not os.path.exists(options.user_data_dir): + os.makedirs(options.user_data_dir) + InvokeCommand(args) + + return 0 # Success + + +if __name__ == '__main__': + return_value = 1 + try: + return_value = main(sys.argv[1:]) + except exceptions.SystemExit: + raise + except Error as error: + print "Error: %s" % error + + sys.exit(return_value) diff --git a/native_client_sdk/src/build_tools/sdk_tools/set_nacl_env.py b/native_client_sdk/src/build_tools/sdk_tools/set_nacl_env.py new file mode 100755 index 0000000..a0c11ca --- /dev/null +++ b/native_client_sdk/src/build_tools/sdk_tools/set_nacl_env.py @@ -0,0 +1,491 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +''' A tool to setup the NaCl build env and invoke a command such as make ''' + +__author__ = 'gwink@google.com (Georges Winkenbach)' + +import optparse +import os +import re +import subprocess +import sys + +# The default sdk platform to use if the user doesn't specify one. +__DEFAULT_SDK_PLATFORM = 'pepper_15' + +# Usage info. +__GLOBAL_HELP = '''%prog options [command] + +set-nacl-env is a utility that sets up the environment required to build +NaCl modules and invokes an optional command in a shell. If no command +is specified, set-nacl-env spawns a new shell instead. Optionally, the user +can request that the settings are printed to stdout. +''' + +# Map the string stored in |sys.platform| into a toolchain host specifier. +__PLATFORM_TO_HOST_MAP = { + 'win32': 'windows', + 'cygwin': 'windows', + 'linux2': 'linux', + 'darwin': 'mac', + } + +# Map key triplet of (host, arch, variant) keys to the corresponding subdir in +# the toolchain path. For instance (mac, x86-32, newlib) maps to mac_x86_newlib. +# Note to NaCl eng.: this map is duplicated in nack_utils.py; you must keep them +# synched. +__HOST_TO_TOOLCHAIN_MAP = { + 'mac': { # Host arch variant + 'x86-32': { + 'newlib': 'mac_x86_newlib', # Mac x86-32 newlib + 'glibc' : 'mac_x86'}, # Mac x86-32 glibc + 'x86-64': { + 'newlib': 'mac_x86_newlib', # Mac x86-64 newlib + 'glibc' : 'mac_x86'}, # Mac x86-64 glibc + }, + 'windows': { + 'x86-32': { + 'newlib': 'win_x86_newlib', # Windows x86-32 newlib + 'glibc' : 'win_x86'}, # Windows x86-32 glibc + 'x86-64': { + 'newlib': 'win_x86_newlib', # Windows x86-64 newlib + 'glibc' : 'win_x86'}, # Windows x86-64 glibc + }, + 'linux': { + 'x86-32': { + 'newlib': 'linux_x86_newlib', # Windows x86-32 newlib + 'glibc' : 'linux_x86'}, # Windows x86-32 glibc + 'x86-64': { + 'newlib': 'linux_x86_newlib', # Windows x86-64 newlib + 'glibc' : 'linux_x86'}, # Windows x86-64 glibc + }, + } + +# Map architecture specification to the corresponding tool-name prefix. +# @private +__VALID_ARCH_SPECS = { + 'x86-32': 'i686', + 'x86-64': 'x86_64', + } + +# Valid lib variants. +__VALID_VARIANT = ['glibc', 'newlib'] + +# Lists of env keys for build tools. Note: Each matching value is actually a +# format template with fields for 'prefix' such as 'i686-nacl-' and 'extras' +# such as ' -m64'. +__BUILD_TOOLS = { + 'CC': '{prefix}gcc{extras}', + 'CXX': '{prefix}g++{extras}', + 'AR': '{prefix}ar{extras}', + 'LINK': '{prefix}g++{extras}', + 'STRIP': '{prefix}strip', + 'RANLIB': '{prefix}ranlib', + } + +# List of env keys for build options with corresponding settings that are +# common to all build configurations. +__BUILD_OPTIONS = { + 'CFLAGS': ['-std=gnu99', '-Wall', '-Wswitch-enum', '-g'], + 'CXXFLAGS': ['-std=gnu++98', '-Wswitch-enum', '-g', '-pthread'], + 'CPPFLAGS': ['-D_GNU_SOURCE=1', '-D__STDC_FORMAT_MACROS=1'], + 'LDFLAGS': [], + } + +# All the build-flags env keys in one list. +__ALL_ENV_KEYS = __BUILD_TOOLS.keys() + __BUILD_OPTIONS.keys() + +# Map build types to the corresponding build flags. +__BUILD_TYPES = { + 'debug': ['-O0'], + 'release': ['-O3'], + } + + +def FormatOptionList(option_list, prefix='', separator=' '): + ''' Format a list of build-option items into a string. + + Format a list of build-option items into a string suitable for output. + + Args: + prefix: a prefix string to prepend to each list item. For instance, + prefix='-D' with item='__DEBUG' generates '-D__DEBUG'. + separator: a separator string to insert between items. + + Returns: + A formatted string. An empty string if list is empty. + ''' + return separator.join([prefix + item for item in option_list]) + + +def GetNaclSdkRoot(): + ''' Produce a string with the full path to the NaCl SDK root. + + The path to nacl-sdk root is derived from one of two sources. First, if + NACL_SDK_ROOT is defined in env it is assumed to contain the desired sdk + root. That makes it possible for this tool to run from any location. If + NACL_SDK_ROOT is not defined or is empty, the sdk root is taken as the + parent directory to this script's file. This works well when this script + is ran from the sdk_tools directory in the standard SDK installation. + + Returns: + A string with the path to the NaCl SDK root. + ''' + if 'NACL_SDK_ROOT' in os.environ: + return os.environ['NACL_SDK_ROOT'] + else: + return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +def GetToolchainPath(options): + ''' Build the path to the toolchain directory. + + Given the host, sdk-root directory, sdk platform, architecture and library + variant, this function builds the path to the toolchain directory. + + Examples: + For + sdk_root == 'c:/cool_code/nacl_sdk' + arch == 'x86-32' + lib variant == 'newlib' + nacl platform = 'pepper_17' + host == 'mac' + this function returns : + toolchain_path == /cool_code/nacl_sdk/pepper_17/toolchain/mac_x86_newlib + + Args: + options: the options instances containing attributes options.host, + options.arch, options.lib_variant, options.sdk_root and + options.sdk_platform. + + Returns: + A string containing the absolute path to the base directory for the + toolchain. + ''' + host = options.host + arch = options.arch + variant = options.lib_variant + toolchain_dir = __HOST_TO_TOOLCHAIN_MAP[host][arch][variant] + base_dir = os.path.abspath(options.sdk_root) + return os.path.join(base_dir, options.sdk_platform, 'toolchain', + toolchain_dir) + + +def ConfigureBaseEnv(merge): + ''' Configure and return a new env instance with the essential options. + + Create and return a new env instance with the base configuration. That env + contains at least an empty entry for each key defined in __ALL_ENV_KEYS. + However, if merge is True, a copy of the current os.environ is used to seed + env. + + Argument: + merge: True ==> merge build configuration with os.environ.. + + Returns: + A base env map. + ''' + env = {} + if merge: + for key, value in os.environ.items(): + env[key] = [value] + # Ensure that every env key has a default definition. + for key in __ALL_ENV_KEYS: + env.setdefault(key, []) + return env + + +def SetBuildTools(env, tool_bin_path, tool_prefix, extras_flags=''): + ''' Configure the build tools build flags in env. + + Given the absolute path to the toolchain's bin directory, tool_prefix and + optional extra_flags build flags, set the entries for the build tools + in env. For instance, using the sample path from GetToolchainPath above and + tool_prefix = 'i686-nacl-' we would get + + env['CC'] = + '/cool_code/nacl_sdk/pepper_17/toolchain/mac_x86_newlib/bin/i686-nacl-gcc' + + Args: + env: the env map to setup. + tool_bin_path: the absolute path to the toolchain's bin directory. + tool_prefix: a string with the tool's prefix, such as 'i686-nacl-'. + extra_flags: optional extra flags, such as ' -m64'. + ''' + for key, val in __BUILD_TOOLS.iteritems(): + tool_name = val.format(prefix=tool_prefix, extras=extras_flags) + env[key] = os.path.join(tool_bin_path, tool_name) + + +def SetRuntimeTools(env, tool_runtime_path): + ''' Setup the runtime tools in env. + + Given an absolute path to the toolchain's runtime directory, setup the + entries for the runtime tools in env. + + Args: + env: the env map to setup. + tool_runtime_path: the absolute path to the toolchain's runtime directory. + ''' + env['NACL_IRT_CORE32'] = os.path.join(tool_runtime_path, + 'irt_core_x86_32.nexe') + env['NACL_IRT_CORE64'] = os.path.join(tool_runtime_path, + 'irt_core_x86_64.nexe') + + +def SetCommonBuildOptions(env, options): + ''' Set the common build options, such as CFLAGS. + + Set the build options, such as CFLAGS that are common to all build + configurations, given the built type. + + Args: + env: the env map to set. + build_type: one of 'debug' or 'release'. + ''' + # Set the build flags from __BUILD_OPTIONS. + for key, val in __BUILD_OPTIONS.iteritems(): + env[key].extend(val) + # Add the build-type specific flags. + env['CFLAGS'].extend(__BUILD_TYPES[options.build_type]) + env['CXXFLAGS'].extend(__BUILD_TYPES[options.build_type]) + if not options.no_ppapi: + env['LDFLAGS'].extend(['-lppapi']) + + +def SetupX86Env(options): + ''' Generate a new env map for X86 builds. + + Generate and return a new env map for x86-NN architecture. The NN bit + size is derived from options.arch. + + Argument: + options: the cmd-line options. + + Returns: + A new env map with the build configuration flags set. + ''' + env = ConfigureBaseEnv(options.merge) + + # Where to find tools and libraries within the toolchain directory. + tool_bin_path = os.path.join(options.toolchain_path, 'bin') + tool_runtime_path = os.path.join(options.toolchain_path, 'runtime') + + # Store the bin paths into env. This isn't really part of the build + # environment. But it's nice to have there for reference. + env['NACL_TOOL_BIN_PATH'] = tool_bin_path + env['NACL_TOOL_RUNTIME_PATH'] = tool_runtime_path + + if options.arch == 'x86-32': + SetBuildTools(env, tool_bin_path, 'i686-nacl-', extras_flags=' -m32') + else: + assert(options.arch == 'x86-64') + SetBuildTools(env, tool_bin_path, 'x86_64-nacl-', extras_flags=' -m64') + SetRuntimeTools(env, tool_runtime_path) + SetCommonBuildOptions(env, options) + return env + + +def dump(options, env, template): + ''' Dump the build settings in env to stdout. + + Args: + options: the cmd-line options, used to output the target buid configuartion. + env: the env map with the build flags. + template: a fiormatting template used to format options output. It must + contain format fields 'option' and 'value'. + ''' + if options.pretty_print: + print '\nConfiguration:' + print '-------------' + print ' Host = %s' % options.host + print ' NaCl SDK root = %s' % options.sdk_root + print ' SDK platform = %s' % options.sdk_platform + print ' Target architecture = %s' % options.arch + print ' Lib variant = %s' % options.lib_variant + + if options.pretty_print: + print '\nNaCl toolchain paths:' + print '-------------------------' + print ' toolchain = %s' % options.toolchain_path + print ' toolchain bin = %s' % env['NACL_TOOL_BIN_PATH'] + print ' toolchain runtime = %s' % env['NACL_TOOL_RUNTIME_PATH'] + + if options.pretty_print: + print '\nBuild tools:' + print '-----------' + print template.format(option='CC', value=env['CC']) + print template.format(option='CXX', value=env['CXX']) + print template.format(option='AR', value=env['AR']) + print template.format(option='LINK', value=env['LINK']) + print template.format(option='STRIP', value=env['STRIP']) + print template.format(option='RANLIB', value=env['RANLIB']) + + if options.pretty_print: + print '\nBuild settings:' + print '--------------' + print template.format(option='CFLAGS', + value=FormatOptionList(option_list=env['CFLAGS'])) + print template.format(option='CXXFLAGS', + value=FormatOptionList(option_list=env['CXXFLAGS'])) + print template.format(option='LDFLAGS', + value=FormatOptionList(option_list=env['LDFLAGS'])) + print template.format(option='CPPFLAGS', + value=FormatOptionList(option_list=env['CPPFLAGS'])) + if options.pretty_print: + print '\nRuntime tools:' + print '-------------' + print template.format(option='NACL_IRT_CORE32', value=env['NACL_IRT_CORE32']) + print template.format(option='NACL_IRT_CORE64', value=env['NACL_IRT_CORE64']) + print '' + + +def NormalizeEnv(env): + ''' Returns a copy of env normalized. + + Internally, this script uses lists to keep track of build settings in env. + This function converts these list to space-separated strings of items, + suitable for use as a subprocess env. + + Argument: + env: env map that must be normalized. + + Returns: + A copy of env with lists converted to strings. + ''' + norm_env = {} + for key, value in env.iteritems(): + if isinstance(value, list): + norm_env[key] = ' '.join(value) + else: + norm_env[key] = value + return norm_env + + +def RunCommandOrShell(cmd_list, env): + ''' Run the command in cmd_list or a shell if cmd_list is empty. + + Run the command in cmd_list using a normalized copy of env. For instance, + cmd_list might contain the items ['make', 'application'], which would + cause command 'make application' to run in the current directory. If cmd_list + is empty, this function will spawn a new sbushell instead. + + Args: + cmd_list: the command list to run. + env: the environment to use. + ''' + # If cmd_list is empty, set it up to spawn a shell instead. If cmd_list + # isn't empty, it will run in the current shell (for security and so that the + # user can see the output). + new_shell = False + if cmd_list: + # Normalize cmd_list by building a list of individual arguments. + new_cmd_list = [] + for item in cmd_list: + new_cmd_list += item.split() + cmd_list = new_cmd_list + else: + # Build a shell command. + new_shell = True + if sys.platform == 'win32': + cmd_list = ['cmd'] + else: + cmd_list = ['/bin/bash', '-s'] + return subprocess.call(cmd_list, env=NormalizeEnv(env), shell=new_shell) + + +def GenerateBuildSettings(options, args): + ''' Generate the build settings and dump them or invoke a command. + + Given the cmd-line options and remaining cmd-line arguments, generate the + required build settings and either dump them to stdout or invoke a shell + command. + + Args: + options: cmd-line options. + args: unconsumed cmd-line arguments. + + Returns: + 0 in case of success or a command result code otherwise. + ''' + # A few generated options, which we store in options for convenience. + options.host = __PLATFORM_TO_HOST_MAP[sys.platform] + options.sdk_root = GetNaclSdkRoot() + options.toolchain_path = GetToolchainPath(options) + + env = SetupX86Env(options) + if options.dump: + dump(options, env, template=options.format_template) + return 0 + else: + return RunCommandOrShell(args, env) + + +def main(argv): + ''' Do main stuff, mainly. + ''' + parser = optparse.OptionParser(usage=__GLOBAL_HELP) + parser.add_option( + '-a', '--arch', dest='arch', + choices=['x86-32', 'x86-64'], + default='x86-64', + help='The target architecture; one of x86-32 or x86-64. ' + '[default = %default.]') + parser.add_option( + '-A', '--no_ppapi', dest='no_ppapi', + default=False, + action='store_true', + help='Do not add -lppapi to the link settings.') + parser.add_option( + '-d', '--dump', dest='dump', + default=False, + action='store_true', + help='Dump the build settings to stdout') + parser.add_option( + '-D', '--pretty_print', dest='pretty_print', + default=False, + action='store_true', + help='Print section headers when dumping to stdout') + parser.add_option( + '-f', '--format_template', dest='format_template', + default=' {option}={value}', + help="The formatting template used to output (option, value) pairs." + "[default='%default.']") + parser.add_option( + '-n', '--no_merge', dest='merge', + default=True, + action='store_false', + help='Do not merge the build options with current environment. By default' + ' %prog merges the build flags with the current environment vars.' + ' This option turns that off.') + parser.add_option( + '-p', '--platform', dest='sdk_platform', + default=__DEFAULT_SDK_PLATFORM, + help='The SDK platform to use; e.g. pepper_16. [default = %default.]') + parser.add_option( + '-t', '--build_type', dest='build_type', + choices=__BUILD_TYPES.keys(), + default='debug', + help='The desired build type; one of debug or release.' + ' [default = %default.]') + parser.add_option( + '-v', '--variant', dest='lib_variant', + choices=['glibc', 'newlib'], default='newlib', + help='The lib variant to use; one of glibc or newlib. ' + '[default = %default.]') + + (options, args) = parser.parse_args(argv) + + # Verify that we're running on a supported host. + if sys.platform not in __PLATFORM_TO_HOST_MAP: + sys.stderr.write('Platform %s is not supported.' % sys.platform) + return 1 + + return GenerateBuildSettings(options, args) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:]))
\ No newline at end of file diff --git a/native_client_sdk/src/build_tools/sdk_tools/update_manifest.py b/native_client_sdk/src/build_tools/sdk_tools/update_manifest.py new file mode 100755 index 0000000..5460943 --- /dev/null +++ b/native_client_sdk/src/build_tools/sdk_tools/update_manifest.py @@ -0,0 +1,391 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +'''Utility to update the SDK manifest file in the build_tools directory''' + +import optparse +import os +import re +import sdk_update +import string +import subprocess +import sys + +HELP='''"Usage: %prog [-b bundle] [options]" + +Actions for particular bundles: + sdk_tools: Upload the most recently built nacl_sdk.zip and sdk_tools.tgz + files to the server and update the manifest file + pepper_??: Download the latest pepper builds off the appropriate branch, + upload these files to the appropriate location on the server, and + update the manifest file. + <others>: Only update manifest file -- you'll need to upload the file yourself +''' + +# Map option keys to manifest attribute key. Option keys are used to retrieve +# option values from cmd-line options. Manifest attribute keys label the +# corresponding value in the manifest object. +OPTION_KEY_MAP = { + # option key manifest attribute key + 'bundle_desc_url': 'desc_url', + 'bundle_revision': sdk_update.REVISION_KEY, + 'bundle_version': sdk_update.VERSION_KEY, + 'desc': 'description', + 'recommended': 'recommended', + 'stability': 'stability', + } +# Map options keys to platform key, as stored in the bundle. +OPTION_KEY_TO_PLATFORM_MAP = { + 'mac_arch_url': 'mac', + 'win_arch_url': 'win', + 'linux_arch_url': 'linux', + 'all_arch_url': 'all', + } + +NACL_SDK_ROOT = os.path.dirname(os.path.dirname(os.path.dirname( + os.path.abspath(__file__)))) + +BUILD_TOOLS_OUT = os.path.join(NACL_SDK_ROOT, 'scons-out', 'build', 'obj', + 'build_tools') + +BUNDLE_SDK_TOOLS = 'sdk_tools' +BUNDLE_PEPPER_MATCHER = re.compile('^pepper_([0-9]+)$') +IGNORE_OPTIONS = set(['gsutil', 'manifest_file', 'upload', 'root_url']) + + +class Error(Exception): + '''Generic error/exception for update_manifest module''' + pass + + +def UpdateBundle(bundle, options): + ''' Update the bundle per content of the options. + + Args: + options: options data. Attributes that are used are also deleted from + options.''' + # Check, set and consume individual bundle options. + for option_key, attribute_key in OPTION_KEY_MAP.iteritems(): + option_val = getattr(options, option_key, None) + if option_val is not None: + bundle[attribute_key] = option_val + delattr(options, option_key); + # Validate what we have so far; we may just avoid going through a lengthy + # download, just to realize that some other trivial stuff is missing. + bundle.Validate() + # Check and consume archive-url options. + for option_key, host_os in OPTION_KEY_TO_PLATFORM_MAP.iteritems(): + platform_url = getattr(options, option_key, None) + if platform_url is not None: + bundle.UpdateArchive(host_os, platform_url) + delattr(options, option_key); + + +class UpdateSDKManifest(sdk_update.SDKManifest): + '''Adds functions to SDKManifest that are only used in update_manifest''' + + def _ValidateBundleName(self, name): + ''' Verify that name is a valid bundle. + + Args: + name: the proposed name for the bundle. + + Return: + True if the name is valid for a bundle, False otherwise.''' + valid_char_set = '()-_.%s%s' % (string.ascii_letters, string.digits) + name_len = len(name) + return (name_len > 0 and all(c in valid_char_set for c in name)) + + def _UpdateManifestVersion(self, options): + ''' Update the manifest version number from the options + + Args: + options: options data containing an attribute self.manifest_version ''' + version_num = int(options.manifest_version) + self._manifest_data['manifest_version'] = version_num + del options.manifest_version + + def _UpdateBundle(self, options): + ''' Update or setup a bundle from the options. + + Args: + options: options data containing at least a valid bundle_name + attribute. Other relevant bundle attributes will also be + used (and consumed) by this function. ''' + # Get and validate the bundle name + if not self._ValidateBundleName(options.bundle_name): + raise Error('Invalid bundle name: "%s"' % options.bundle_name) + bundle_name = options.bundle_name + del options.bundle_name + # Get the corresponding bundle, or create it. + bundle = self.GetBundle(bundle_name) + if not bundle: + bundle = sdk_update.Bundle(bundle_name) + self.SetBundle(bundle) + UpdateBundle(bundle, options) + + def _VerifyAllOptionsConsumed(self, options, bundle_name): + ''' Verify that all the options have been used. Raise an exception if + any valid option has not been used. Returns True if all options have + been consumed. + + Args: + options: the object containing the remaining unused options attributes. + bundl_name: The name of the bundle, or None if it's missing.''' + # Any option left in the list should have value = None + for key, val in options.__dict__.items(): + if val != None and key not in IGNORE_OPTIONS: + if bundle_name: + raise Error('Unused option "%s" for bundle "%s"' % (key, bundle_name)) + else: + raise Error('No bundle name specified') + return True; + + def UpdateManifest(self, options): + ''' Update the manifest object with values from the command-line options + + Args: + options: options object containing attribute for the command-line options. + Note that all the non-trivial options are consumed. + ''' + # Go over all the options and update the manifest data accordingly. + # Valid options are consumed as they are used. This gives us a way to + # verify that all the options are used. + if options.manifest_version is not None: + self._UpdateManifestVersion(options) + # Keep a copy of bundle_name, which will be consumed by UpdateBundle, for + # use in _VerifyAllOptionsConsumed below. + bundle_name = options.bundle_name + if bundle_name is not None: + self._UpdateBundle(options) + self._VerifyAllOptionsConsumed(options, bundle_name) + self._ValidateManifest() + + +class GsUtil(object): + def __init__(self, gsutil): + '''gsutil is the path to the gsutil executable''' + self.gsutil = gsutil + self.root = 'gs://nativeclient-mirror/nacl/nacl_sdk' + + def GetURI(self, path): + '''Return the full gs:// URI for a given relative path''' + return '/'.join([self.root, path]) + + def Run(self, command): + '''Runs gsutil with a given argument list and returns exit status''' + args = [self.gsutil] + command + print 'GSUtil.Run(%s)' % args + sys.stdout.flush() + return subprocess.call(args) + + def CheckIfExists(self, path): + '''Check whether a given path exists on commondatastorage + + Args: + path: path relative to SDK root directory on the server + + Returns: True if it exists, False if it does not''' + # todo(mball): Be a little more intelligent about this check and compare + # the output strings against expected values + return self.Run(['ls', self.GetURI(path)]) == 0 + + def Copy(self, source, destination): + '''Copies a given source file to a destination path and makes it readable + + Args: + source: path to source file on local filesystem + destination: path to destination, relative to root directory''' + args = ['cp', '-a', 'public-read', source, self.GetURI(destination)] + if self.Run(args) != 0: + raise Error('Unable to copy %s to %s' % (source, destination)) + + +class UpdateSDKManifestFile(sdk_update.SDKManifestFile): + '''Adds functions to SDKManifestFile that are only used in update_manifest''' + + def __init__(self, options): + '''Create a new SDKManifest object with default contents. + + If |json_filepath| is specified, and it exists, its contents are loaded and + used to initialize the internal manifest. + + Args: + json_filepath: path to json file to read/write, or None to write a new + manifest file to stdout. + ''' + # Strip-off all the I/O-based options that do not relate to bundles + self._json_filepath = options.manifest_file + self.gsutil = GsUtil(options.gsutil) + self.options = options + self._manifest = UpdateSDKManifest() + if self._json_filepath: + self._LoadFile() + + def _HandleSDKTools(self): + '''Handles the sdk_tools bundle''' + # General sanity checking of parameters + SDK_TOOLS_FILES = ['sdk_tools.tgz', 'nacl_sdk.zip'] + options = self.options + if options.bundle_version is None: + options.bundle_version = sdk_update.MAJOR_REV + if options.bundle_version != sdk_update.MAJOR_REV: + raise Error('Specified version (%s) does not match MAJOR_REV (%s)' % + (options.bundle_version, sdk_update.MAJOR_REV)) + if options.bundle_revision is None: + options.bundle_revision = sdk_update.MINOR_REV + if options.bundle_revision != sdk_update.MINOR_REV: + raise Error('Specified revision (%s) does not match MINOR_REV (%s)' % + (options.bundle_revision, sdk_update.MINOR_REV)) + version = '%s.%s' % (options.bundle_version, options.bundle_revision) + # Update the remaining options + if options.desc is None: + options.desc = ('Native Client SDK Tools, revision %s.%s' % + (options.bundle_version, options.bundle_revision)) + options.recommended = options.recommended or 'yes' + options.stability = options.stability or 'stable' + if options.upload: + # Check whether the tools already exist + for name in SDK_TOOLS_FILES: + path = '/'.join([version, name]) + if self.gsutil.CheckIfExists(path): + raise Error('File already exists at %s' % path) + # Upload the tools files to the server + for name in SDK_TOOLS_FILES: + source = os.path.join(BUILD_TOOLS_OUT, name) + destination = '/'.join([version, name]) + self.gsutil.Copy(source, destination) + url = '/'.join([options.root_url, version, 'sdk_tools.tgz']) + options.mac_arch_url = options.mac_arch_url or url + options.linux_arch_url = options.linux_arch_url or url + options.win_arch_url = options.win_arch_url or url + + def _HandlePepper(self): + '''Handles the pepper bundles''' + options = self.options + match = BUNDLE_PEPPER_MATCHER.match(options.bundle_name) + if match is not None: + options.bundle_version = int(match.group(1)) + if options.bundle_version is None: + raise Error('Need to specify a bundle version') + if options.bundle_revision is None: + raise Error('Need to specify a bundle revision') + if options.desc is None: + options.desc = ('Chrome %s bundle, revision %s' % + (options.bundle_version, options.bundle_revision)) + root_url = '%s/pepper_%s_%s' % (options.root_url, options.bundle_version, + options.bundle_revision) + options.mac_arch_url = '/'.join([root_url, 'naclsdk_mac.tgz']) + options.linux_arch_url = '/'.join([root_url, 'naclsdk_linux.tgz']) + options.win_arch_url = '/'.join([root_url, 'naclsdk_win.exe']) + + def HandleBundles(self): + '''Handles known bundles by automatically uploading files''' + bundle_name = self.options.bundle_name + if bundle_name == BUNDLE_SDK_TOOLS: + self._HandleSDKTools() + elif bundle_name.startswith('pepper'): + self._HandlePepper() + + def UpdateWithOptions(self): + ''' Update the manifest file with the given options. Create the manifest + if it doesn't already exists. Raises an Error if the manifest doesn't + validate after updating. + + Args: + options: option data''' + # UpdateManifest does not know how to deal with file-related options + self._manifest.UpdateManifest(self.options) + self.WriteFile() + + +def main(argv): + '''Main entry for update_manifest.py''' + + buildtools_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + parser = optparse.OptionParser(usage=HELP) + + # Setup options + parser.add_option( + '-b', '--bundle-version', dest='bundle_version', + type='int', + default=None, + help='Required: Version number for the bundle.') + parser.add_option( + '-B', '--bundle-revision', dest='bundle_revision', + type='int', + default=None, + help='Required: Revision number for the bundle.') + parser.add_option( + '-d', '--description', dest='desc', + default=None, + help='Required: Description for this bundle.') + parser.add_option( + '-f', '--manifest-file', dest='manifest_file', + default=os.path.join(buildtools_dir, 'json', + sdk_update.MANIFEST_FILENAME), + help='location of manifest file to read and update') + parser.add_option( + '-g', '--gsutil', dest='gsutil', + default='gsutil', help='location of gsutil tool for uploading bundles') + parser.add_option( + '-L', '--linux-archive', dest='linux_arch_url', + default=None, + help='URL for the Linux archive.') + parser.add_option( + '-M', '--mac-archive', dest='mac_arch_url', + default=None, + help='URL for the Mac archive.') + parser.add_option( + '-n', '--bundle-name', dest='bundle_name', + default=None, + help='Required: Name of the bundle.') + parser.add_option( + '-r', '--recommended', dest='recommended', + choices=sdk_update.YES_NO_LITERALS, + default=None, + help='Required: whether this bundle is recommended. one of "yes" or "no"') + parser.add_option( + '-R', '--root-url', dest='root_url', + default='http://commondatastorage.googleapis.com/nativeclient-mirror/' + 'nacl/nacl_sdk', + help='Root url for uploading') + parser.add_option( + '-s', '--stability', dest='stability', + choices=sdk_update.STABILITY_LITERALS, + default=None, + help='Required: Stability for this bundle; one of. ' + '"obsolete", "post_stable", "stable", "beta", "dev", "canary".') + parser.add_option( + '-u', '--desc-url', dest='bundle_desc_url', + default=None, + help='Optional: URL to follow to read additional bundle info.') + parser.add_option( + '-U', '--upload', dest='upload', default=False, action='store_true', + help='Indicates whether to upload bundle to server') + parser.add_option( + '-v', '--manifest-version', dest='manifest_version', + type='int', + default=None, + help='Required for new manifest files: ' + 'Version number for the manifest.') + parser.add_option( + '-W', '--win-archive', dest='win_arch_url', + default=None, + help='URL for the Windows archive.') + + # Parse options and arguments and check. + (options, args) = parser.parse_args(argv) + if len(args) > 0: + parser.error('These arguments were not understood: %s' % args) + + manifest_file = UpdateSDKManifestFile(options) + manifest_file.HandleBundles() + manifest_file.UpdateWithOptions() + + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/native_client_sdk/src/build_tools/tar_archive.py b/native_client_sdk/src/build_tools/tar_archive.py new file mode 100644 index 0000000..7d29340 --- /dev/null +++ b/native_client_sdk/src/build_tools/tar_archive.py @@ -0,0 +1,156 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Class that produces a table of contents form a tar archive.""" + +import os +import tarfile + +from build_tools import path_set + + +class Error(Exception): + pass + + +class TarArchive(path_set.PathSet): + '''Container for a tar archive table of contents. + + The table of contents is an enumeration of each node in the archive, stored + as the attributes of a PathSet (see path_set.py for details). + + The tar archive can be taken directly from a tarball (as long as the format + is supported by the tarfile module), or from a manifest file that was + generated using tar -tv. + + Attributes: + path_filter: A callable that gets applied to all paths in the object's tar + archive before the paths are added to any of the sets or dictionaries. + Setting this callable to None has the same effect as using a + pass-through filter (such as lambda x: x). This property cannot be + deleted. + ''' + + def __init__(self): + path_set.PathSet.__init__(self) + self._path_filter = os.path.normpath + + @property + def path_filter(self): + '''A filter to apply to paths in the tar archive.''' + return self._path_filter + + @path_filter.setter + def path_filter(self, path_filter): + self._path_filter = path_filter or (lambda x: x) + + @path_filter.deleter + def path_filter(self): + raise Error('path_filter cannot be deleted') + + def InitWithTarFile(self, tar_archive_file): + '''Initialize the object using a tar-format archive file. + + Wipes out any old table of contents data and replaces it with the new table + of contents from |tar_archive_file|. If the given tar archive doesn't + exist, or if it is not in a recognizable format, this method does nothing. + + Args: + tar_archive_file: The archive file. This is expected to be a file in a + tar format that the python tarfile module recognizes. + Raises: + OSError if |tar_archive_file| doesn't exist. + ''' + def MakePathSet(condition): + '''Helper function used with a lambda to generate a set of path names.''' + return set([self.path_filter(tarinfo.name) + for tarinfo in tar_archive if condition(tarinfo)]) + + def MakeLinksDict(condition): + '''Helper function used with a lambda to generate the link dicitonaries. + + Note that accessing tarinfo.linkname raises an exception if + the TarInfo member is not a link, which is why there are two separate + helper functions. + ''' + return dict([(self.path_filter(tarinfo.name), + self.path_filter(tarinfo.linkname)) + for tarinfo in tar_archive if condition(tarinfo)]) + + if not os.path.exists(tar_archive_file): + raise OSError('%s does not exist' % tar_archive_file) + tar_archive = None + try: + tar_archive = tarfile.open(tar_archive_file) + self.files = MakePathSet(lambda x: x.isfile()) + self.dirs = MakePathSet(lambda x: x.isdir()) + self.symlinks = MakeLinksDict(lambda x: x.issym()) + self.links = MakeLinksDict(lambda x: x.islnk()) + finally: + if tar_archive: + tar_archive.close() + + def InitWithManifest(self, tar_manifest_file): + '''Parse a tar-style manifest file and return the table of contents. + + Wipes out any old table of contents data and replaces it with the new table + of contents from |tar_manifest_file|. If the given manifest file doesn't + exist this method does nothing. + + Args: + tar_manifest_file: The manifest file. This is expected to be a file that + contains the result of tar -tv on the associated tarball. + Raises: + OSError if |tar_archive_file| doesn't exist. + ''' + # Index values into the manifest entry list. Note that some of these + # indices are negative (counting back from the last element), this is + # because the intermediate fields (such as date) from tar -tv differ from + # platform to platform. The last fields in the link members are always the + # same, however. All symlinks end in ['link_src', '->', 'link']; all hard + # links end in ['link_src', 'link', 'to', 'link']. + PERM_BITS_INDEX = 0 + # The symbolic link name is the third-from-last entry. + SYMLINK_FILENAME_INDEX = -3 + # The hard link name is the fourth-from-last entry. + HLINK_FILENAME_INDEX = -4 + LAST_ITEM_INDEX = -1 + + if not os.path.exists(tar_manifest_file): + raise OSError('%s does not exist' % tar_manifest_file) + self.Reset() + with open(tar_manifest_file) as manifest: + for manifest_item in map(lambda line: line.split(), manifest): + # Parse a single tar -tv entry in a manifest. + # The tar -tv entry is represented as a list of strings. The first + # string represents the permission bits; the first bit indicates the + # kind of entry. Depending on the kind of entry, other fields represent + # the member's path name, link source, etc. An entry list might look + # like this: + # ['hrwxr-xr-x', 'Administrators/Domain', 'Users', '0', '2011-08-09', + # '08:11', 'toolchain/win_x86/bin/i686-nacl-g++.exe', 'link', 'to', + # 'toolchain/win_x86/bin/i686-nacl-addr2line.exe'] + # This example is a hard link from i686-nacl-addr2line.exe to + # i686-nacl-g++.exe. + file_type = manifest_item[PERM_BITS_INDEX][0] + if file_type == 'd': + # A directory: the name is the final element of the entry. + self.dirs.add(self.path_filter(manifest_item[LAST_ITEM_INDEX])) + elif file_type == 'h': + # A hard link: the last element is the source of the hard link, and is + # entered as the key in the |links| dictionary. + link_name = self.path_filter(manifest_item[HLINK_FILENAME_INDEX]) + self.links[link_name] = self.path_filter( + manifest_item[LAST_ITEM_INDEX]) + elif file_type == 'l': + # A symbolic link: the last element is the source of the symbolic + # link. + link_name = self.path_filter(manifest_item[SYMLINK_FILENAME_INDEX]) + self.symlinks[link_name] = self.path_filter( + manifest_item[LAST_ITEM_INDEX]) + else: + # Everything else is considered a plain file. + self.files.add(self.path_filter(manifest_item[LAST_ITEM_INDEX])) + diff --git a/native_client_sdk/src/build_tools/tests/__init__.py b/native_client_sdk/src/build_tools/tests/__init__.py new file mode 100644 index 0000000..395f4ed --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/__init__.py @@ -0,0 +1,7 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Package for build_tools/tests""" diff --git a/native_client_sdk/src/build_tools/tests/apply_patch_test.py b/native_client_sdk/src/build_tools/tests/apply_patch_test.py new file mode 100644 index 0000000..bb15fc3 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/apply_patch_test.py @@ -0,0 +1,249 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for apply_patch.py.""" + +import os +import sys +import unittest +import tempfile + +from build_tools import apply_patch + + +class TestRange(unittest.TestCase): + """ Test class _Range. """ + + def setUp(self): + pass + + def testParseValidRange(self): + """ Test _Range.Parse with a valid range. """ + diff_line = ['@@ -1,10 +5,9 @@'] + range = apply_patch._Range() + range.Parse(diff_line) + self.assertEqual(range.src_start_line, 1) + self.assertEqual(range.src_line_count, 10) + self.assertEqual(range.dest_start_line, 5) + self.assertEqual(range.dest_line_count, 9) + + def testParseValidRangeWithDefaults(self): + """ Test _Range.Parse with a valid range with default counts. """ + diff_line = ['@@ -1 +5 @@'] + range = apply_patch._Range() + range.Parse(diff_line) + self.assertEqual(range.src_start_line, 1) + self.assertEqual(range.src_line_count, 1) + self.assertEqual(range.dest_start_line, 5) + self.assertEqual(range.dest_line_count, 1) + + def testParseInvalidRanges(self): + """ Test _Range.Parse with invalid ranges. """ + diff_line = ['@@ ,10 +5,9 @@'] + range = apply_patch._Range() + self.assertRaises(apply_patch.Error, range.Parse, diff_line) + diff_line = ['@@ -,10 +5,9 @@'] + range = apply_patch._Range() + self.assertRaises(apply_patch.Error, range.Parse, diff_line) + diff_line = ['@@ -1,10 5,9 @@'] + range = apply_patch._Range() + self.assertRaises(apply_patch.Error, range.Parse, diff_line) + diff_line = ['@ -5,10 +5,9 @@'] + range = apply_patch._Range() + self.assertRaises(apply_patch.Error, range.Parse, diff_line) + + +class TestChangeHunk(unittest.TestCase): + """ Test class _ChangeHunk. """ + + def setUp(self): + self._change_hunk = apply_patch._ChangeHunk() + + def testDiffLineType(self): + """ Test function _ChangeHunk._DiffLineType. """ + diff_line_pairs = [ + [' a contextual line', apply_patch.CONTEXTUAL_DIFF_LINE], + ['\n', apply_patch.CONTEXTUAL_DIFF_LINE], + ['\r', apply_patch.CONTEXTUAL_DIFF_LINE], + [ '\n\r', apply_patch.CONTEXTUAL_DIFF_LINE], + ['+one added line', apply_patch.ADDED_DIFF_LINE], + ['-one delete line', apply_patch.DELETED_DIFF_LINE], + ['not valid', apply_patch.NOT_A_DIFF_LINE], + ['---not valid', apply_patch.NOT_A_DIFF_LINE], + ['+++not valid', apply_patch.NOT_A_DIFF_LINE], + ] + for pair in diff_line_pairs: + self.assertEqual(self._change_hunk._DiffLineType(pair[0]), pair[1]) + + def testParseWithValidLines(self): + """ Test function _ChangeHunk.Parse with valid diff lines. """ + diff_lines = [ + '@@ -5,4 +10,4 @@', + ' A contextual line.', + '-A line deleted from source.', + '-Another deleted line.', + '\r', # An empty contextual line. + '+A line added to destination.', + '+Another added line.', + '--- Begin next header.', + ] + num_input_lines = len(diff_lines) + self._change_hunk.Parse(diff_lines) + + self.assertEqual(self._change_hunk.range.src_start_line, 5) + self.assertEqual(self._change_hunk.range.src_line_count, 4) + self.assertEqual(self._change_hunk.range.dest_start_line, 10) + self.assertEqual(self._change_hunk.range.dest_line_count, 4) + self.assertEqual(len(self._change_hunk.lines), num_input_lines - 2) + + def testParseWithInvalidLines(self): + """ Test function _ChangeHunk.Parse with invalid diff lines. """ + diff_lines = [ + '@@ -5,4 +10,4 @@', + ' A contextual line.', + '-Another deleted line.', + '\r', # An empty contextual line. + '+A line added to destination.', + '+Another added line.', + '--- Begin next header.', + ] + num_input_lines = len(diff_lines) + self.assertRaises(apply_patch.Error, self._change_hunk.Parse, diff_lines) + + def testApply(self): + """ Test function _ChangeHunk.Apply. """ + with tempfile.SpooledTemporaryFile() as in_file: + with tempfile.SpooledTemporaryFile() as out_file: + in_file.write('aaaaaaaaaa\n') + in_file.write('bbbbbbbbbb\n') + in_file.seek(0) + + diff_lines = [ + '@@ -1,2 +1,2 @@', + ' aaaaaaaaaa\n', + '-bbbbbbbbbb\n', + '+cccccccccc\n', + '--- Begin next header.\n', + ] + + self._change_hunk.Parse(diff_lines) + self._change_hunk.Apply(0, in_file, out_file) + out_file.seek(0) + self.assertEqual(out_file.readline(), 'aaaaaaaaaa\n'); + self.assertEqual(out_file.readline(), 'cccccccccc\n'); + +class TestPatchHeader(unittest.TestCase): + """ Test class _PatchHeader. """ + + def setUp(self): + self._header = apply_patch._PatchHeader() + + def testParseValidHeader(self): + """ Test function _PatchHeader.Parse with a valid header. """ + diff_lines = [ + '--- dir/file.txt 1969-12-31 17:00:00.000000000 -0700\n', + '+++ dir/file_new.txt 2010-07-08 09:49:37.000000000 -0600\n' + ] + self._header.Parse(diff_lines) + self.assertEqual(self._header.in_file_name, 'dir/file.txt') + self.assertEqual(self._header.out_file_name, 'dir/file_new.txt') + + def testParseInvalidHeader(self): + """ Test function _PatchHeader.Parse with invalid headers. """ + diff_lines = [ + '-- dir/file.txt 1969-12-31 17:00:00.000000000 -0700\n', + '+++ dir/file_new.txt 2010-07-08 09:49:37.000000000 -0600\n' + ] + self.assertRaises(apply_patch.Error, self._header.Parse, diff_lines) + diff_lines = [ + '---\n', + '+++ dir/file_new.txt 2010-07-08 09:49:37.000000000 -0600\n' + ] + self.assertRaises(apply_patch.Error, self._header.Parse, diff_lines) + diff_lines = [ + '-- dir/file.txt 1969-12-31 17:00:00.000000000 -0700\n', + '+++\n' + ] + self.assertRaises(apply_patch.Error, self._header.Parse, diff_lines) + diff_lines = [ + '@@ dir/file.txt 1969-12-31 17:00:00.000000000 -0700\n', + '+++ dir/file_new.txt 2010-07-08 09:49:37.000000000 -0600\n' + ] + self.assertRaises(apply_patch.Error, self._header.Parse, diff_lines) + diff_lines = [ + '--- dir/file.txt 1969-12-31 17:00:00.000000000 -0700\n', + '@@ dir/file_new.txt 2010-07-08 09:49:37.000000000 -0600\n' + ] + self.assertRaises(apply_patch.Error, self._header.Parse, diff_lines) + +class TestPatch(unittest.TestCase): + """ Test class _Patch. """ + + TEST_DIR = 'apply_patch_test_archive' + TEST_FILE = 'original_file.txt' + TEST_DATA = [ + 'aaaaaaaaaa\n', + 'bbbbbbbbbb\n', + 'cccccccccc\n', + 'dddddddddd\n', + 'eeeeeeeeee\n', + 'ffffffffff\n', + ] + + def setUp(self): + self._patch = apply_patch._Patch() + + def testParseAndApply(self): + """ Test function _Patch.Parse. """ + diff_lines = [ + '--- ' + TestPatch.TEST_DIR + '/' + TestPatch.TEST_FILE + '\n', + '+++ ' + TestPatch.TEST_DIR + '/new' + TestPatch.TEST_FILE + '\n', + '@@ -1,0 +1,1 @@\n', + '+zzzzzzzzzz\n', + '@@ -3,2 +3,2 @@\n', + ' cccccccccc\n', + '-dddddddddd\n', + '+----------\n', + ] + self._patch.Parse(diff_lines) + + # Create the original data file. + script_dir = os.path.dirname(__file__) + test_dir = os.path.join(script_dir, TestPatch.TEST_DIR) + file_path = os.path.join(test_dir, TestPatch.TEST_FILE) + if not os.path.exists(test_dir): + os.mkdir(test_dir) + with open(file_path, 'w+b') as file: + file.truncate(0) + file.writelines(TestPatch.TEST_DATA) + + # Apply the patch and verify patched file + patched_data = [ + 'zzzzzzzzzz\n', + 'aaaaaaaaaa\n', + 'bbbbbbbbbb\n', + 'cccccccccc\n', + '----------\n', + 'eeeeeeeeee\n', + 'ffffffffff\n', + ] + self._patch.Apply(script_dir) + with open(file_path) as file: + self.assertEqual(file.readline(), patched_data.pop(0)) + + +def RunTests(): + + outcome = True + for test_class in [TestRange, TestChangeHunk, TestPatchHeader, TestPatch]: + suite = unittest.TestLoader().loadTestsFromTestCase(test_class) + result = unittest.TextTestRunner(verbosity=2).run(suite) + outcome = outcome and result.wasSuccessful() + + return int(not outcome) + +if __name__ == '__main__': + sys.exit(RunTests()) diff --git a/native_client_sdk/src/build_tools/tests/build_utils_test.py b/native_client_sdk/src/build_tools/tests/build_utils_test.py new file mode 100755 index 0000000..1347d48 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/build_utils_test.py @@ -0,0 +1,104 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for build_utils.py.""" + +__author__ = 'mball@google.com (Matt Ball)' + +import platform +import os +import subprocess +import sys +import tarfile +import unittest + +from build_tools import build_utils +import mox + + +class TestBuildUtils(unittest.TestCase): + """This class tests basic functionality of the build_utils package""" + def setUp(self): + self.mock_factory = mox.Mox() + + def testArchitecture(self): + """Testing the Architecture function""" + bit_widths = build_utils.SupportedNexeBitWidths() + # Make sure word-width of either 32 or 64. + self.assertTrue(32 in bit_widths or 64 in bit_widths) + if sys.platform in ['linux', 'linux2']: + self.assertTrue(32 in bit_widths) + if '64' in platform.machine(): + self.assertTrue(64 in bit_widths) + elif sys.platform == 'darwin': + # Mac should have both 32- and 64-bit support. + self.assertTrue(32 in bit_widths) + self.assertTrue(64 in bit_widths) + else: + # Windows supports either 32- or 64-bit, but not both. + self.assertEqual(1, len(bit_widths)) + + def testBotAnnotatorPrint(self): + """Testing the Print function of the BotAnnotator class""" + stdout_mock = self.mock_factory.CreateMock(sys.stdout) + stdout_mock.write("My Bot Message\n") + stdout_mock.flush() + stdout_mock.write("BUILD_STEP MyBuildStep\n") + stdout_mock.flush() + self.mock_factory.ReplayAll() + bot = build_utils.BotAnnotator(stdout_mock) + bot.Print("My Bot Message") + bot.BuildStep("MyBuildStep") + self.mock_factory.VerifyAll() + + def testBotAnnotatorRun(self): + """Testing the 'Run' command of the BotAnnotator class""" + out_string = 'hello' + print_command = ['python', '-c', + "import sys; sys.stdout.write('%s')" % out_string] + error_command = ['python', '-c', "import sys; sys.exit(1)"] + stdout_mock = self.mock_factory.CreateMock(sys.stdout) + stdout_mock.write('Running %s\n' % print_command) + stdout_mock.flush() + stdout_mock.write('%s\n' % out_string) + stdout_mock.flush() + stdout_mock.write('Running %s\n' % error_command) + stdout_mock.flush() + stdout_mock.write('\n') + stdout_mock.flush() + self.mock_factory.ReplayAll() + bot = build_utils.BotAnnotator(stdout_mock) + run_output = bot.Run(print_command) + self.assertEqual(run_output, "%s" % out_string) + self.assertRaises(subprocess.CalledProcessError, bot.Run, error_command) + self.mock_factory.VerifyAll() + + def testJoinPathToNaClRepo(self): + """Testing the 'JoinPathToNaClRepo' utility function.""" + # Test an empty arg list. + test_dir = os.path.join('third_party', 'native_client') + self.assertEqual(test_dir, build_utils.JoinPathToNaClRepo()) + # Test an empty arg list with just the root_dir key set. + test_dir = os.path.join('test_root', test_dir) + self.assertEqual(test_dir, + build_utils.JoinPathToNaClRepo(root_dir='test_root')) + # Test non-empty arg lists and with and without root_dir. + test_dir = os.path.join('third_party', 'native_client', 'testing', 'file') + self.assertEqual(test_dir, + build_utils.JoinPathToNaClRepo('testing', 'file')) + test_dir = os.path.join('test_root', test_dir) + self.assertEqual(test_dir, + build_utils.JoinPathToNaClRepo('testing', 'file', root_dir='test_root')) + + +def RunTests(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestBuildUtils) + result = unittest.TextTestRunner(verbosity=2).run(suite) + + return int(not result.wasSuccessful()) + +if __name__ == '__main__': + sys.exit(RunTests()) diff --git a/native_client_sdk/src/build_tools/tests/fake_gsutil.bat b/native_client_sdk/src/build_tools/tests/fake_gsutil.bat new file mode 100644 index 0000000..86cc640 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/fake_gsutil.bat @@ -0,0 +1,3 @@ +@echo off
+rem Simple Wrapper function to allow running fake_gsutil on Windows
+python "%~dp0fake_gsutil.py" %*
diff --git a/native_client_sdk/src/build_tools/tests/fake_gsutil.py b/native_client_sdk/src/build_tools/tests/fake_gsutil.py new file mode 100755 index 0000000..f145162 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/fake_gsutil.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +'''Fake implementation of gsutils, which is the utility used to upload files +to commondatastorage''' + +import optparse +import sys + + +def HandleLS(args): + if len(args) == 0: + print 'gs://nativeclient-upload/' + return 0 + print ('InvalidUriError: Attempt to get key for "%s" failed. ' + 'This probably indicates the URI is invalid.' % args[0]) + return 1 + + +def UnknownCommand(args): + return 0 + + +def HandleCP(args): + return 0 + + +def main(args): + if len(args) == 0: + return 0 + COMMANDS = { + 'ls': HandleLS, + 'cp': HandleCP, + } + return COMMANDS.get(args[0], UnknownCommand)(args[1:]) + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/native_client_sdk/src/build_tools/tests/install_nsis_test.py b/native_client_sdk/src/build_tools/tests/install_nsis_test.py new file mode 100644 index 0000000..b9d0c34 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/install_nsis_test.py @@ -0,0 +1,103 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for install_nsis.py.""" + +import os +import shutil +import subprocess +import sys +import unittest + +from build_tools import install_nsis + + +class TestInstallNsis(unittest.TestCase): + """This class tests basic functionality of the install_nsis package""" + def setUp(self): + self.nsis_installer_ = os.path.join(os.path.abspath('build_tools'), + install_nsis.NSIS_INSTALLER) + self.target_dir_ = os.path.join(os.path.dirname(self.nsis_installer_), + 'nsis_test', + 'NSIS') + def tearDown(self): + shutil.rmtree(os.path.dirname(self.target_dir_), ignore_errors=True) + + def testNsisInstallerExists(self): + """Ensure that the correct version of NSIS is present.""" + self.assertTrue(os.path.exists(self.nsis_installer_)) + + def testBogusNsisInstaller(self): + """Make sure the installer handles invalid directory names.""" + self.assertRaises(IOError, install_nsis.InstallNsis, 'bogus', 'not_a_dir') + + def testNsisInstaller(self): + """Make sure the installer produces an NSIS directory.""" + install_nsis.InstallNsis(self.nsis_installer_, self.target_dir_) + self.assertTrue(os.path.exists(os.path.join(self.target_dir_, + 'makensis.exe'))) + + def testAccessControlExtensions(self): + """Make sure that the AccessControl extensions can be installed.""" + script_dir = os.path.dirname(self.nsis_installer_) + install_nsis.InstallAccessControlExtensions( + script_dir, + os.path.join(script_dir, install_nsis.ACCESS_CONTROL_ZIP), + self.target_dir_) + self.assertTrue(os.path.exists( + os.path.join(self.target_dir_, 'Plugins', 'AccessControl.dll'))) + self.assertTrue(os.path.exists( + os.path.join(self.target_dir_, 'Plugins', 'AccessControlW.dll'))) + + def testMkLinkExtensions(self): + """Make sure the MkLink extensions are installed.""" + script_dir = os.path.dirname(self.nsis_installer_) + install_nsis.InstallMkLinkExtensions( + os.path.join(script_dir, install_nsis.MKLINK_DLL), + self.target_dir_) + self.assertTrue(os.path.exists( + os.path.join(self.target_dir_, 'Plugins', 'MkLink.dll'))) + + def testForceTargetInstall(self): + """Test that a force install to a target directory works.""" + try: + # Mock the NSIS install directories so that Install() thinks NSIS is + # already installed. + os.makedirs(os.path.join(self.target_dir_, 'Plugins'), mode=0777) + except OSError: + pass + self.assertFalse(os.path.exists(os.path.join(self.target_dir_, + 'makensis.exe'))) + self.assertFalse(os.path.exists( + os.path.join(self.target_dir_, 'Plugins', 'AccessControl.dll'))) + self.assertFalse(os.path.exists( + os.path.join(self.target_dir_, 'Plugins', 'AccessControlW.dll'))) + self.assertFalse(os.path.exists( + os.path.join(self.target_dir_, 'Plugins', 'MkLink.dll'))) + + install_nsis.Install(os.path.dirname(self.nsis_installer_), + target_dir=self.target_dir_, + force=True) + + self.assertTrue(os.path.exists(os.path.join(self.target_dir_, + 'makensis.exe'))) + self.assertTrue(os.path.exists( + os.path.join(self.target_dir_, 'Plugins', 'AccessControl.dll'))) + self.assertTrue(os.path.exists( + os.path.join(self.target_dir_, 'Plugins', 'AccessControlW.dll'))) + self.assertTrue(os.path.exists( + os.path.join(self.target_dir_, 'Plugins', 'MkLink.dll'))) + + +def RunTests(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestInstallNsis) + result = unittest.TextTestRunner(verbosity=2).run(suite) + + return int(not result.wasSuccessful()) + + +if __name__ == '__main__': + sys.exit(RunTests()) diff --git a/native_client_sdk/src/build_tools/tests/installer_contents_test.py b/native_client_sdk/src/build_tools/tests/installer_contents_test.py new file mode 100755 index 0000000..79858a7 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/installer_contents_test.py @@ -0,0 +1,66 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for installer_contents.py.""" + +import os +import sys +import unittest + +from build_tools import installer_contents + + +class TestInstallerContents(unittest.TestCase): + """This class tests basic functionality of the installer_contents package""" + def setUp(self): + self.mock_path_list = ['file', + 'file/in/a/path', + 'dir/spec/', + '', + '/abs/path', + ] + + + def testConvertToOSPaths(self): + output = installer_contents.ConvertToOSPaths(self.mock_path_list) + self.assertEqual(len(output), len(self.mock_path_list)) + self.assertEqual(output[0], 'file') + self.assertEqual(output[1], os.path.join('file', 'in', 'a', 'path')) + self.assertEqual(output[2], os.path.join('dir', 'spec', '')) + self.assertEqual(output[3], '') + self.assertEqual(output[4], os.path.join('abs', 'path')) + + def testGetDirectoriesFromPathList(self): + output = installer_contents.GetDirectoriesFromPathList(self.mock_path_list) + self.assertEqual(1, len(output)) + self.assertEqual(output[0], os.path.join('dir', 'spec', '')) + + def testGetFilesFromPathList(self): + output = installer_contents.GetFilesFromPathList(self.mock_path_list) + self.assertEqual(4, len(output)) + self.assertEqual(output[0], 'file') + self.assertEqual(output[1], os.path.join('file', 'in', 'a', 'path')) + self.assertEqual(output[2], '') + self.assertEqual(output[3], os.path.join('abs', 'path')) + + def testGetToolchainManifest(self): + self.assertRaises(KeyError, + installer_contents.GetToolchainManifest, + 'notatoolchain') + newlib_manifest_path = installer_contents.GetToolchainManifest('newlib') + self.assertTrue(os.path.exists(newlib_manifest_path)) + glibc_manifest_path = installer_contents.GetToolchainManifest('glibc') + self.assertTrue(os.path.exists(glibc_manifest_path)) + + +def RunTests(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestInstallerContents) + result = unittest.TextTestRunner(verbosity=2).run(suite) + + return int(not result.wasSuccessful()) + +if __name__ == '__main__': + sys.exit(RunTests()) diff --git a/native_client_sdk/src/build_tools/tests/installer_test.py b/native_client_sdk/src/build_tools/tests/installer_test.py new file mode 100755 index 0000000..3026619 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/installer_test.py @@ -0,0 +1,480 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" Tests for the SDK Installer + +The general structure is as follows: + + 1. Extract the installer into a given temporary directory + 2. Run tests -- See TestSDK class (e.g., testExamples) + 3. Remove the installer directory +""" + +from __future__ import with_statement + +import cStringIO +import datetime +import httplib +import optparse +import os +import platform +import shutil +import socket +import string +import subprocess +import sys +import time +import unittest +import urllib +import zipfile + +from build_tools import build_utils +from build_tools import html_checker +from build_tools.sdk_tools import sdk_update +from build_tools.sdk_tools import update_manifest + +annotator = build_utils.BotAnnotator() + + +def TestingClosure(_outdir, _jobs): + '''This closure provides the variables needed by the various tests + + Args: + _outdir: The output directory that holds the extracted installer + _jobs: Number of parallel jobs for Make or Scons + + Returns: + A TestCase class that can be used by the unittest loader + ''' + toolchain_base = build_utils.NormalizeToolchain(base_dir=_outdir) + toolchain_bin = os.path.join(toolchain_base, 'bin') + toolchain_runtime = os.path.join(toolchain_base, 'runtime') + gcc64 = os.path.join(toolchain_bin, 'x86_64-nacl-gcc') + sel_ldr64 = os.path.join(toolchain_bin, 'sel_ldr_x86_64') + irt_core64 = os.path.join(toolchain_runtime, 'irt_core_x86_64.nexe') + scons = 'scons.bat' if sys.platform == 'win32' else 'scons' + staging_path = os.path.join(_outdir, 'staging') + + class TestSDK(unittest.TestCase): + '''Contains tests that run within an extracted SDK installer''' + + def GetBotShellEnv(self): + '''Massage |env| so its env variables will work on the bots.''' + env = os.environ.copy() + env['NACL_TARGET_PLATFORM'] = '.' # Use the repo's toolchain. + env['NACL_PROJECT_ROOT'] = _outdir # Put project templates here. + env['NACL_SDK_ROOT'] = _outdir + return env + + def SconsCommand(self, scons_path=''): + '''Helper to cons up a scons command. Sets nacl platform to '.' so that + SCons uses the repo's toolchain. + ''' + return [os.path.join(scons_path, scons), + '-j', _jobs, + '--nacl-platform=.' + ] + + def buildExamplesWithFlags(self, flags=None): + '''A small helper function that runs scons with arch and variant flags. + + Args: + flags: Any extra flags to pass to scons. Must be an array, + can be empty. + ''' + flags = flags or [] + path = os.path.join(_outdir, 'examples') + command = self.SconsCommand(path) + flags + env = self.GetBotShellEnv() + annotator.Run(command, cwd=path, env=env) + + def testBuildExamplesVariant(self): + '''Verify non-default toolchain SDK example build.''' + + # Note that --nacl-platform is set to ".". This is done so that the bots + # will use the repo's toolchain, instead of a platform-specific one. + # There are no platform-specific toolchains in the repo. + self.buildExamplesWithFlags(['--architecture=x86', + '--variant=newlib']) + self.buildExamplesWithFlags(['--architecture=x86', + '--variant=glibc']) + print "Test with bogus architectures, variants." + print "We expect these tests to throw exceptions:" + self.assertRaises(subprocess.CalledProcessError, + self.buildExamplesWithFlags, + flags=['--architecture=nosucharch']) + self.assertRaises(subprocess.CalledProcessError, + self.buildExamplesWithFlags, + flags=['--variant=nosuchvariant']) + + def testStagedHtmlFiles(self): + self.buildExamplesWithFlags(['--architecture=x86', '--variant=glibc']) + html_checker.ValidateAllLinks([os.path.join(staging_path, 'index.html')]) + + def testReadMe(self): + '''Check that the current build version and date are in the README file''' + + filename = 'README.txt' if sys.platform == 'win32' else 'README' + with open(os.path.join(_outdir, filename), 'r') as file: + contents = file.read() + version = 'Version: %s' % build_utils.PLATFORM_VERSION + annotator.Print('Checking that SDK version = %s' % version) + self.assertTrue(contents.count(version) == 1, + 'Version mismatch in %s' % filename) + revision = 'Revision: %s' % str(build_utils.SVNRevision()) + annotator.Print('Checking that SDK revision = %s' % revision) + self.assertTrue(contents.count(revision) == 1, + 'Revision mismatch in %s' % filename) + + # Check that the README contains either the current date or yesterday's + # date (which happens when building over midnight) + self.assertEqual( + 1, + contents.count(str(datetime.date.today())) + + contents.count(str(datetime.date.today() - + datetime.timedelta(days=1))), + "Cannot find today's or yesterday's date in README") + + def testRunHelloWorldUnittest(self): + '''Verify that we can build and run the hello_world unit test.''' + bit_widths = build_utils.SupportedNexeBitWidths() + test_targets = ['test%d' % bits for bits in bit_widths] + + if len(test_targets) == 0: + annotator.Print('No test targets found') + return + print 'running targets: %s' % str(test_targets) + path = os.path.join(_outdir, 'examples', 'hello_world') + command = self.SconsCommand(os.path.join(path, '..')) + test_targets + env = self.GetBotShellEnv() + annotator.Run(command, cwd=path, env=env) + + def testHttpd(self): + '''Test the simple HTTP server. + + Run the simple server and make sure it quits when processing an URL that + has the ?quit=1 parameter set. This test runs the server on the default + port (5103) and on a specified port. + ''' + + DEFAULT_SERVER_PORT = 5103 + + def runAndQuitHttpServer(port=DEFAULT_SERVER_PORT, + alternate_cwd=None, + extra_args=[], + should_fail=False): + '''A small helper function to launch the simple HTTP server. + + This function launches the simple HTTP server, then waits for its + banner output to appear. If the banner doesn't appear within 10 + seconds, the test fails. The banner is checked validate that it + displays the right port number. + + Once the server is verified as running, this function sends it a GET + request with the ?quit=1 URL parmeter. It then waits to see if the + server process exits with a return code of 0. If the server process + doesn't exit within 20 seconds, the test fails. + + Args: + port: The port to use, defaults to 5103. + ''' + path = staging_path + command = [sys.executable, os.path.join(path, 'httpd.py')] + # Add the port only if it's not the default. + if port != DEFAULT_SERVER_PORT: + command += [str(port)] + command += extra_args + # Can't use annotator.Run() because the HTTP server doesn't stop, which + # causes Run() to hang. + annotator.Print('Starting server: %s' % command) + annotator.Print('extra_args=%s' % str(extra_args)) + current_working_dir = path if alternate_cwd is None else alternate_cwd + annotator.Print('cwd=%s' % current_working_dir) + process = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + cwd=current_working_dir, + env=self.GetBotShellEnv()) + self.assertNotEqual(None, process) + # Wait until the process starts by trying to send it a GET request until + # the server responds, or until the timeout expires. If the timeout + # expires, fail the test. + time_start = time.time() + time_now = time_start + timeout_time = time_start + 20 # 20 sec timeout. + output = '' + conn = None + while time_now < timeout_time: + conn = httplib.HTTPConnection('localhost', port) + try: + # Send the quit request. + conn.request("GET", "/?quit=1") + output = process.stdout.readline() + break + except socket.error: + # If the server is not listening for connections, then + # HTTPConnetion() raises a socket exception, not one of the + # exceptions defined in httplib. In order to resend a request in + # this case, the original connection has to be closed and re-opened. + conn.close() + conn = None + time.sleep(1) # Wait a second to try again. + time_now = time.time() + + # If we expect the test to fail (e.g. bad directory without + # --no_dir_check) then there should be no connection. + if should_fail: + self.assertEqual(None, conn) + return_code = process.poll() + # If the process has not terminated, return_code will be None + # but since the server should have failed to launch, it should + # have terminated by now. + self.assertNotEqual(return_code, None) + return + + self.assertNotEqual(None, conn) + # Validate the first line of the startup banner. An example of the + # full line is: + # INFO:root:Starting local server on port 5103 + self.assertTrue(output.startswith('INFO:root:Starting')) + self.assertEqual(1, output.count(str(port))) + annotator.Print('Server startup banner: %s' % output) + + # Close down the connection and wait for the server to quit. + conn.getresponse() + conn.close() + + # Check to see if the server quit properly. It should quit within + # 0.5 seconds, so if the first poll() indicates that the process is + # still running, wait 1 sec and then poll again. If the process is + # still running after 20 sec, then fail the test. + return_code = process.poll() + poll_count = 0 + while return_code == None and poll_count < 20: + time.sleep(1) + return_code = process.poll() + poll_count += 1 + self.assertEqual(0, return_code) + + runAndQuitHttpServer() + runAndQuitHttpServer(5280) + # Make sure server works outside examles with --no_dir_check. + runAndQuitHttpServer(5281, alternate_cwd=_outdir, + extra_args=['--no_dir_check']) + # Make sure it works in examples with --no_dir_check. + runAndQuitHttpServer(5281, extra_args=['--no_dir_check']) + # Make sure the test fails if --no_dir_check is left out and the CWD + # is not examples. + runAndQuitHttpServer(5281, alternate_cwd=_outdir, should_fail=True) + # Retest port 5281 with the default parameters. + runAndQuitHttpServer(5281) + + def testProjectTemplates(self): + '''Create and build projects from project_templates.''' + + def initAndCompileProject(project_name, flags=[]): + '''A small helper function that runs init_project.py and then runs + a scons build in the resulting directory. + + Args: + project_name: The project's name, set the --name= parameter for + init_project to this value. + flags: Any extra flags to pass to init_project. Must be an array, + can be empty. + ''' + path = os.path.join(_outdir, 'project_templates') + project_path = os.path.join(_outdir, project_name) + scons_command = self.SconsCommand(project_path) + init_project_command = [sys.executable, + 'init_project.py', + '--name=%s' % project_name, + '--nacl-platform=.'] + flags + env = self.GetBotShellEnv() + annotator.Run(init_project_command, cwd=path, env=env) + annotator.Run(scons_command, cwd=project_path, env=env) + + initAndCompileProject('test_c_project', flags=['-c']) + initAndCompileProject('test_cc_project') + # Calling init_project again with the same names should cause an error + # because the project already exists, and we don't overwrite projects. + print "Rerunning init_project again to test overwriting previous project." + print "We expect these tests to throw exceptions:" + self.assertRaises(subprocess.CalledProcessError, + initAndCompileProject, + 'test_c_project', + flags=['-c']) + self.assertRaises(subprocess.CalledProcessError, + initAndCompileProject, + 'test_cc_project') + + # valgrind is deprecated as of pepper_17. + def deprecatedTestValgrind(self): + '''Verify that Valgrind works properly (Linux 64-bit only)''' + + bit_widths = build_utils.SupportedNexeBitWidths() + if (not sys.platform.startswith('linux')) or (64 not in bit_widths): + annotator.Print('Not running on 64-bit Linux -- skip') + return + true_basename = os.path.join(_outdir, 'true') + true_c_filename = '%s.c' % true_basename + true_nexe_filename = '%s.nexe' % true_basename + with open(true_c_filename, 'w') as true_file: + true_file.write('int main(void) { return 0; }\n') + annotator.Run([gcc64, '-o', true_nexe_filename, '-m64', '-O0', + '-Wl,-u,have_nacl_valgrind_interceptors', '-g', + true_c_filename, '-lvalgrind']) + memcheck = os.path.join(_outdir, 'third_party', 'valgrind', 'memcheck.sh') + annotator.Run([memcheck, sel_ldr64, + # The -B flag is needed by sel_ldr to find an Integrated + # Runtime that it can use to run .nexes. + '-B', irt_core64, + '-Q', true_nexe_filename]) + + return TestSDK + + +def ExtractInstaller(installer, outdir, bundle_name, nacl_sdk): + '''Extract the SDK installer into a given directory + + If the outdir already exists, then this function deletes it + + Args: + installer: full path of the SDK installer + outdir: output directory where to extract the installer + bundle_name: name of sdk bundle within outdir + nacl_sdk: filename of nacl_sdk tarball + + Raises: + OSError - if the outdir already exists + CalledProcessError - if the extract operation fails + ''' + + annotator.Print('Extracting installer %s into %s' % (installer, outdir)) + + if os.path.exists(outdir): + RemoveDir(outdir) + + os.mkdir(outdir) + + if sys.platform == 'win32': + zip_file = None + try: + zip_file = zipfile.ZipFile(nacl_sdk) + zip_file.extractall(path=outdir) + finally: + if zip_file: + zip_file.close() + else: + # Unfortunately, the zipfile module does not retain file permissions + # when extracting executable scripts. For now, just use the unzip + # that comes with Linux and Mac. + subprocess.check_call(['unzip', nacl_sdk], cwd=outdir) + + outdir = os.path.join(outdir, 'nacl_sdk') + + manifest_filename = os.path.join(outdir, 'test_manifest.json') + update_manifest_options = [ + '--bundle-revision=1', + '--bundle-version=2', + '--description=installer_test bundle', + '--%s-archive=file://%s' % ( + sdk_update.GetHostOS(), + urllib.pathname2url(os.path.abspath(installer))), + '--bundle-name=%s' % bundle_name, + '--recommended=yes', + '--stability=stable', + '--manifest-version=1', + '--manifest-file=%s' % manifest_filename] + annotator.Print('Running update manifest with %s' % update_manifest_options) + if 0 != update_manifest.main(update_manifest_options): + raise Exception('update_manifest terminated abnormally.') + + naclsdk_options = [ + os.path.join(outdir, + 'naclsdk.bat' if sys.platform == 'win32' else 'naclsdk'), + '--manifest-url=file://%s' % urllib.pathname2url(manifest_filename), + '--sdk-root-dir=%s' % outdir, + '--user-data-dir=%s' % outdir, + 'update'] + annotator.Print('Running naclsdk with %s' % naclsdk_options) + subprocess.check_call(naclsdk_options) + + +def RemoveDir(outdir): + '''Removes the given directory + + On Unix systems, this just runs shutil.rmtree, but on Windows, this doesn't + work when the directory contains junctions (as does our SDK installer). + Therefore, on Windows, it runs rmdir /S /Q as a shell command. This always + does the right thing on Windows. + + Args: + outdir: The directory to delete + + Raises: + CalledProcessError - if the delete operation fails on Windows + OSError - if the delete operation fails on Linux + ''' + + annotator.Print('Removing %s' % outdir) + if sys.platform == 'win32': + subprocess.check_call(['rmdir /S /Q', outdir], shell=True) + else: + shutil.rmtree(outdir) + + +def main(): + '''Main entry for installer tests + + Returns: + 0: Success + 1: Failure + + Also, raises various exceptions for error conditions. + ''' + + BUNDLE_NAME = 'test_sdk_bundle' + parser = optparse.OptionParser( + usage='Usage: %prog [options] sdk_installer') + parser.add_option( + '-o', '--outdir', dest='outdir', default='sdk_temp_dir', + help='temporary output directory for holding the installer') + parser.add_option( + '-j', '--jobs', dest='jobs', default=1, + help='number of parallel jobs to run') + parser.add_option( + '-n', '--nacl-sdk', dest='nacl_sdk', default='nacl_sdk.zip', + help='location of the nacl_sdk tarball') + parser.add_option( + '-s', '--sdk-tools', dest='sdk_tools', default='sdk_tools.tgz', + help='location of the sdk_tools tarball') + + options, args = parser.parse_args() + + if len(args) == 0: + parser.error('Must provide an sdk_installer') + + if len(args) > 1: + parser.error('Must provide only one sdk_installer') + + installer = args[0] + outdir = os.path.abspath(options.outdir) + + annotator.Print("Running with installer = %s, outdir = %s, jobs = %s" % ( + installer, outdir, options.jobs)) + ExtractInstaller(installer, outdir, BUNDLE_NAME, options.nacl_sdk) + + suite = unittest.TestLoader().loadTestsFromTestCase( + TestingClosure(os.path.join(outdir, 'nacl_sdk', BUNDLE_NAME), + options.jobs)) + result = unittest.TextTestRunner(verbosity=2).run(suite) + + RemoveDir(outdir) + + return int(not result.wasSuccessful()) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/native_client_sdk/src/build_tools/tests/naclsdk_manifest_test.json b/native_client_sdk/src/build_tools/tests/naclsdk_manifest_test.json new file mode 100644 index 0000000..a1eb57ea --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/naclsdk_manifest_test.json @@ -0,0 +1,89 @@ +{ + "manifest_version": 1, + "bundles": [ + { + "name": "sdk_tools", + "revision": 1, + "version": 2, + "description": "Native Client SDK Tools, revision 1", + "stability": "stable", + "recommended": "yes", + "archives": [ + { + "host_os": "linux", + "size": 30018483, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_sdk_linux_1.tgz" + }, + { + "host_os": "mac", + "size": 30227408, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_sdk_mac_1.tgz" + }, + { + "host_os": "win", + "size": 36440315, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_sdk_win_1.tgz" + } + ] + }, + { + "name": "test_1", + "version": 1, + "revision": 2, + "description": "Test Bundle version 1", + "stability": "stable", + "recommended": "yes", + "archives": [ + { + "host_os": "linux", + "size": 30018483, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_linux_pepper_14_1.tgz" + }, + { + "host_os": "mac", + "size": 30227408, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_mac_pepper_14_1.tgz" + }, + { + "host_os": "win", + "size": 36440315, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_win_pepper_14_1.tgz" + } + ] + }, + { + "name": "test_2", + "version": 1, + "revision": 2, + "description": "Test Bundle version 2", + "stability": "stable", + "recommended": "yes", + "archives": [ + { + "host_os": "linux", + "size": 30018483, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_linux_pepper_15_1.tgz" + }, + { + "host_os": "mac", + "size": 30227408, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_mac_pepper_15_1.tgz" + }, + { + "host_os": "win", + "size": 36440315, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_win_pepper_15_1.tgz" + } + ] + } + ] +} diff --git a/native_client_sdk/src/build_tools/tests/nsis_script_test.py b/native_client_sdk/src/build_tools/tests/nsis_script_test.py new file mode 100644 index 0000000..877bde1 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/nsis_script_test.py @@ -0,0 +1,147 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for nsis_script.py.""" + +import filecmp +import os +import sys +import tarfile +import tempfile +import unittest + +from build_tools import nsis_script + + +class TestNsisScript(unittest.TestCase): + """This class tests basic functionality of the nsis_script package""" + + def FilterSvn(self, list): + '''A filter used to remove .svn dirs from installer lists.''' + return [elt for elt in list if '.svn' not in elt] + + def testConstructor(self): + """Test default constructor.""" + script = nsis_script.NsisScript('test_script.nsi') + self.assertEqual('test_script.nsi', script.script_file) + self.assertEqual(0, len(script.files)) + self.assertEqual(0, len(script.dirs)) + self.assertEqual(0, len(script.symlinks)) + self.assertEqual(0, len(script.links)) + + def testInstallDir(self): + """Test install directory accessor/mutator.""" + script = nsis_script.NsisScript('test_script.nsi') + test_inst_dir = os.path.join('C:%s' % os.sep, 'test_install_dir') + script.install_dir = test_inst_dir + self.assertEqual(test_inst_dir, script.install_dir) + + def testBadInstallDir(self): + """Test install directory mutator with bad data""" + script = nsis_script.NsisScript('test_script.nsi') + try: + script.install_dir = 'bogus_path' + self.fail('install_dir failed to throw an exception with bogus_path') + except nsis_script.Error: + pass + else: + raise + + def testInitFromDirectory(self): + """Test creation of artifact lists from an archive directory.""" + script = nsis_script.NsisScript('test_script.nsi') + archive_dir = os.path.join('build_tools', 'tests', 'nsis_test_archive') + script.InitFromDirectory(archive_dir, + dir_filter=self.FilterSvn, + file_filter=self.FilterSvn) + file_set = script.files + self.assertEqual(3, len(file_set)) + self.assertTrue(os.path.join(archive_dir, 'test_file.txt') in file_set) + self.assertTrue(os.path.join(archive_dir, 'test_dir', 'test_dir_file1.txt') + in file_set) + self.assertTrue(os.path.join(archive_dir, 'test_dir', 'test_dir_file2.txt') + in file_set) + dir_set = script.dirs + self.assertEqual(1, len(dir_set)) + self.assertTrue(os.path.join(archive_dir, 'test_dir') in dir_set) + + def testCreateInstallNameScript(self): + """Test the install name include script.""" + test_dir = os.path.join('build_tools', 'tests') + script = nsis_script.NsisScript(os.path.join(test_dir, 'test_script.nsi')) + script.CreateInstallNameScript(cwd=test_dir) + install_name_script = open(os.path.join(test_dir, 'sdk_install_name.nsh'), + 'r') + self.assertTrue(script.install_dir in install_name_script.read()) + install_name_script.close() + os.remove(os.path.join(test_dir, 'sdk_install_name.nsh')) + + def testNormalizeInstallPath(self): + """Test NormalizeInstallPath.""" + # If InitFromDirectory() is not called, then install paths are unchanged. + test_dir = os.path.join('build_tools', 'tests') + script = nsis_script.NsisScript(os.path.join(test_dir, 'test_script.nsi')) + test_path = os.path.join('C:', 'test', 'path') + path = script.NormalizeInstallPath(test_path) + self.assertEqual(test_path, path) + # Set a relative install path. + archive_dir = os.path.join(test_dir, 'nsis_test_archive') + script.InitFromDirectory(archive_dir, + dir_filter=self.FilterSvn, + file_filter=self.FilterSvn) + test_path = os.path.join('test', 'relative', 'path') + path = script.NormalizeInstallPath(os.path.join(archive_dir, test_path)) + self.assertEqual(test_path, path) + + def testCreateSectionNameScript(self): + """Test the section name script.""" + test_dir = os.path.join('build_tools', 'tests') + script = nsis_script.NsisScript(os.path.join(test_dir, 'test_script.nsi')) + archive_dir = os.path.join(test_dir, 'nsis_test_archive') + script.InitFromDirectory(archive_dir, + dir_filter=self.FilterSvn, + file_filter=self.FilterSvn) + script.CreateSectionNameScript(cwd=test_dir) + # When comparing the contents of the script with the golden file, note + # that the 'File' lines can be in any order. All the other section commands + # have to be in the correct order. + def GetSectionCommands(section_script): + '''Split all the section commands into File and other commands. + + Returns: + A tuple (commands, file_set), where |commands| is an ordered list of + NSIS section commands, and |file_set| is an unordered set of NSIS File + commands. + ''' + commands = [] + file_set = set() + with open(section_script) as script_file: + commands = script_file.readlines(); # All the commands in order. + file_set = set([file_cmd + for file_cmd in commands if 'File ' in file_cmd]) + # Remove the File commands from the in-order command list. + for file in file_set: + commands.remove(file) + return commands, file_set + + test_commands, test_files = GetSectionCommands( + os.path.join(test_dir, 'test_sdk_section.nsh')) + script_commands, script_files = GetSectionCommands( + os.path.join(test_dir, 'sdk_section.nsh')) + self.assertEqual(test_commands, script_commands) + self.assertEqual(test_files, script_files) # Uses set() equality. + os.remove(os.path.join(test_dir, 'sdk_section.nsh')) + + +def RunTests(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestNsisScript) + result = unittest.TextTestRunner(verbosity=2).run(suite) + + return int(not result.wasSuccessful()) + + +if __name__ == '__main__': + sys.exit(RunTests()) diff --git a/native_client_sdk/src/build_tools/tests/nsis_test_archive/test_dir/test_dir_file1.txt b/native_client_sdk/src/build_tools/tests/nsis_test_archive/test_dir/test_dir_file1.txt new file mode 100644 index 0000000..96fc3a7 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/nsis_test_archive/test_dir/test_dir_file1.txt @@ -0,0 +1,3 @@ +This is a test file for nsis_script_test.py + +It is file 1 within a directory.
\ No newline at end of file diff --git a/native_client_sdk/src/build_tools/tests/nsis_test_archive/test_dir/test_dir_file2.txt b/native_client_sdk/src/build_tools/tests/nsis_test_archive/test_dir/test_dir_file2.txt new file mode 100644 index 0000000..f448ca9 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/nsis_test_archive/test_dir/test_dir_file2.txt @@ -0,0 +1,3 @@ +This is a test file for nsis_script_test.py + +It is file 2 within a directory.
\ No newline at end of file diff --git a/native_client_sdk/src/build_tools/tests/nsis_test_archive/test_file.txt b/native_client_sdk/src/build_tools/tests/nsis_test_archive/test_file.txt new file mode 100644 index 0000000..83066e0 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/nsis_test_archive/test_file.txt @@ -0,0 +1 @@ +This is a test file for nsis_script_test.py
\ No newline at end of file diff --git a/native_client_sdk/src/build_tools/tests/path_set_test.py b/native_client_sdk/src/build_tools/tests/path_set_test.py new file mode 100644 index 0000000..3b6d115 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/path_set_test.py @@ -0,0 +1,174 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for path_set.py.""" + +import os +import sys +import unittest + +from build_tools import path_set + + +class TestPathSet(unittest.TestCase): + """This class tests basic functionality of the installer_contents package""" + + def setUp(self): + self.pathset = path_set.PathSet() + + def testConstructor(self): + """Test default constructor.""" + self.assertEqual(0, len(self.pathset.files)) + self.assertEqual(0, len(self.pathset.dirs)) + self.assertEqual(0, len(self.pathset.symlinks)) + self.assertEqual(0, len(self.pathset.links)) + + def testSetAttributes(self): + self.pathset.files = set(['file1', 'file2']) + self.pathset.dirs = set(['dir1', 'dir2']) + self.pathset.symlinks = {'symlink': 'symlink_target'} + self.pathset.links = {'link': 'link_target'} + self.assertEqual(2, len(self.pathset.files)) + self.assertTrue('file1' in self.pathset.files) + self.assertTrue('file2' in self.pathset.files) + self.assertFalse('dir1' in self.pathset.files) + self.assertEqual(2, len(self.pathset.dirs)) + self.assertTrue('dir1' in self.pathset.dirs) + self.assertTrue('dir2' in self.pathset.dirs) + self.assertFalse('file1' in self.pathset.dirs) + self.assertEqual(1, len(self.pathset.symlinks)) + self.assertTrue('symlink' in self.pathset.symlinks) + self.assertFalse('dir1' in self.pathset.symlinks) + self.assertEqual(1, len(self.pathset.links)) + self.assertTrue('link' in self.pathset.links) + self.assertFalse('file1' in self.pathset.links) + self.pathset.dirs.discard('dir1') + self.assertFalse('dir11' in self.pathset.dirs) + + def testSetBadAttributes(self): + try: + self.pathset.files = ['this', 'is', 'a', 'list'] + self.fail('set files failed to throw an exception with non-set') + except path_set.Error: + pass + else: + raise + + try: + self.pathset.dirs = ['dir', 'list'] + self.fail('set dirs failed to throw an exception with non-set') + except path_set.Error: + pass + else: + raise + + try: + self.pathset.symlinks = 10 + self.fail('set symlinks failed to throw an exception with non-dict') + except path_set.Error: + pass + else: + raise + + try: + self.pathset.links = 'string of links' + self.fail('set links failed to throw an exception with non-dict') + except path_set.Error: + pass + else: + raise + + def testOrOperator(self): + self.pathset.files = set(['file1', 'file2', 'file3']) + self.pathset.dirs = set(['dir1', 'dir2']) + self.pathset.symlinks = {'symlink': 'symlink_target'} + self.pathset.links = {'link': 'link_target'} + pathset2 = path_set.PathSet() + pathset2.files = set(['file1', 'file4']) + pathset2.dirs = set(['dir1', 'dir3']) + pathset2.symlinks = {'symlink2': 'symlink_target2', + 'dir2': 'link_to_dir', + 'file3': 'link_to_file'} + pathset2.links = {'link2': 'link_target2'} + merged_pathset = self.pathset | pathset2 + self.assertFalse('file3' in self.pathset.files) + self.assertFalse('dir3' in self.pathset.dirs) + self.assertTrue('file1' in merged_pathset.files) + self.assertTrue('file2' in merged_pathset.files) + self.assertFalse('file3' in merged_pathset.files) + self.assertTrue('file4' in merged_pathset.files) + self.assertFalse('dir1' in merged_pathset.files) + self.assertTrue('dir1' in merged_pathset.dirs) + self.assertFalse('dir2' in merged_pathset.dirs) + self.assertTrue('dir3' in merged_pathset.dirs) + self.assertFalse('file1' in merged_pathset.dirs) + self.assertTrue('symlink' in merged_pathset.symlinks) + self.assertTrue('symlink2' in merged_pathset.symlinks) + self.assertFalse('dir1' in merged_pathset.symlinks) + self.assertTrue('link' in merged_pathset.links) + self.assertTrue('link2' in merged_pathset.links) + self.assertFalse('file1' in merged_pathset.links) + + def testOrEqualsOperator(self): + self.pathset.files = set(['file1', 'file2', 'file3']) + self.pathset.dirs = set(['dir1', 'dir2']) + self.pathset.symlinks = {'symlink': 'symlink_target'} + self.pathset.links = {'link': 'link_target'} + pathset2 = path_set.PathSet() + pathset2.files = set(['file1', 'file4']) + pathset2.dirs = set(['dir1', 'dir3']) + pathset2.symlinks = {'symlink2': 'symlink_target2', + 'dir2': 'link_to_dir', + 'file3': 'link_to_file'} + pathset2.links = {'link2': 'link_target2'} + self.pathset |= pathset2 + self.assertTrue('file1' in self.pathset.files) + self.assertTrue('file2' in self.pathset.files) + self.assertFalse('file3' in self.pathset.files) + self.assertFalse('dir1' in self.pathset.files) + self.assertTrue('dir1' in self.pathset.dirs) + self.assertFalse('dir2' in self.pathset.dirs) + self.assertTrue('dir3' in self.pathset.dirs) + self.assertFalse('file1' in self.pathset.dirs) + self.assertTrue('symlink' in self.pathset.symlinks) + self.assertTrue('symlink2' in self.pathset.symlinks) + self.assertFalse('dir1' in self.pathset.symlinks) + self.assertTrue('link' in self.pathset.links) + self.assertTrue('link2' in self.pathset.links) + self.assertFalse('file1' in self.pathset.links) + + def testPrependPath(self): + path_prefix = os.path.join('C:%s' % os.sep, 'path', 'prefix') + self.pathset.files = set(['file1', 'file2']) + self.pathset.dirs = set(['dir1', 'dir2']) + self.pathset.symlinks = {'symlink': 'symlink_target'} + self.pathset.links = {'link': 'link_target'} + prepended_files = set([os.path.join(path_prefix, f) + for f in self.pathset.files]) + prepended_dirs = set([os.path.join(path_prefix, d) + for d in self.pathset.dirs]) + prepended_symlinks = {os.path.join(path_prefix, 'symlink'): + 'symlink_target'} + prepended_links = {os.path.join(path_prefix, 'link'): 'link_target'} + self.pathset.PrependPath(path_prefix) + self.assertTrue(isinstance(self.pathset.files, set)) + self.assertTrue(isinstance(self.pathset.dirs, set)) + self.assertTrue(isinstance(self.pathset.symlinks, dict)) + self.assertTrue(isinstance(self.pathset.links, dict)) + self.assertEqual(prepended_files, self.pathset.files) + self.assertEqual(prepended_dirs, self.pathset.dirs) + self.assertEqual(prepended_symlinks, self.pathset.symlinks) + self.assertEqual(prepended_links, self.pathset.links) + + +def RunTests(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestPathSet) + result = unittest.TextTestRunner(verbosity=2).run(suite) + + return int(not result.wasSuccessful()) + +if __name__ == '__main__': + sys.exit(RunTests()) diff --git a/native_client_sdk/src/build_tools/tests/sdk_test_cache/naclsdk_manifest.json b/native_client_sdk/src/build_tools/tests/sdk_test_cache/naclsdk_manifest.json new file mode 100644 index 0000000..77e1e4e --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/sdk_test_cache/naclsdk_manifest.json @@ -0,0 +1,70 @@ +{ + "manifest_version": 1, + "bundles": [ + { + "name": "sdk_tools", + "revision": 1, + "version": 2, + "description": "Native Client SDK Tools, revision 1", + "stability": "stable", + "recommended": "yes", + "archives": [ + { + "host_os": "linux", + "size": 30018483, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_sdk_linux_1.tgz" + }, + { + "host_os": "mac", + "size": 30227408, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_sdk_mac_1.tgz" + }, + { + "host_os": "win", + "size": 36440315, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_sdk_win_1.tgz" + } + ] + }, + { + "name": "test_1", + "version": 1, + "revision": 1, + "description": "Test Bundle version 1", + "stability": "stable", + "recommended": "yes", + "archives": [ + { + "host_os": "linux", + "size": 30018483, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_linux_pepper_14_1.tgz" + }, + { + "host_os": "mac", + "size": 30227408, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_mac_pepper_14_1.tgz" + }, + { + "host_os": "win", + "size": 36440315, + "checksum": { "sha1": "638bf0020c6f013ebef420d03fd7bb28593047b4" }, + "url": "nacl_win_pepper_14_1.tgz" + } + ] + }, + { + "name": "empty_bundle", + "version": 0, + "revision": 0, + "description": "Empty Test Bundle, Version 0", + "stability": "dev", + "recommended": "no", + "archives": [] + } + ] +} diff --git a/native_client_sdk/src/build_tools/tests/sdk_update_test.py b/native_client_sdk/src/build_tools/tests/sdk_update_test.py new file mode 100755 index 0000000..3196379 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/sdk_update_test.py @@ -0,0 +1,185 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for sdk_update.py.""" + +import exceptions +import mox +import os +import subprocess +import sys +import tempfile +import unittest +import urllib + +from build_tools.sdk_tools import sdk_update + + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +PARENT_DIR = os.path.dirname(SCRIPT_DIR) + + +class FakeOptions(object): + ''' Just a placeholder for options ''' + pass + + +def CallSDKUpdate(args): + '''Calls the sdk_update.py utility and returns stdout as a string + + Args: + args: command-line arguments as a list (not including program name) + + Returns: + string tuple containing (stdout, stderr) + + Raises: + subprocess.CalledProcessError: non-zero return code from sdk_update''' + command = ['python', os.path.join(PARENT_DIR, 'sdk_tools', + 'sdk_update.py')] + args + process = subprocess.Popen(stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + args=command) + output, error_output = process.communicate() + + retcode = process.poll() # Note - calling wait() can cause a deadlock + if retcode != 0: + raise subprocess.CalledProcessError(retcode, command) + return output, error_output + + +class TestSDKUpdate(unittest.TestCase): + ''' Test basic functionality of the sdk_update package ''' + def setUp(self): + self._options = FakeOptions() + self._options.manifest_url = 'file://%s' % urllib.pathname2url( + os.path.join(SCRIPT_DIR, 'naclsdk_manifest_test.json')) + self._options.user_data_dir = os.path.join(SCRIPT_DIR, 'sdk_test_cache') + + def testBadArg(self): + '''Test that using a bad argument results in an error''' + self.assertRaises(subprocess.CalledProcessError, CallSDKUpdate, ['--bad']) + + def testGetHostOS(self): + '''Test that the GetHostOS function returns a valid value''' + self.assertTrue(sdk_update.GetHostOS() in ['linux', 'mac', 'win']) + + def testHelp(self): + '''Test that basic help works''' + # Running any help command should call sys.exit() + self.assertRaises(exceptions.SystemExit, sdk_update.main, ['-h']) + + def testList(self): + '''Test the List function''' + command = ['--user-data-dir=%s' % + os.path.join(SCRIPT_DIR, 'sdk_test_cache'), + '--manifest-url=file://%s' % + urllib.pathname2url(os.path.join( + SCRIPT_DIR, 'naclsdk_manifest_test.json')), + 'list'] + bundle_list = CallSDKUpdate(command)[0] + # Just do some simple sanity checks on the resulting string + self.assertEqual(bundle_list.count('sdk_tools'), 2) + self.assertEqual(bundle_list.count('test_1'), 2) + self.assertEqual(bundle_list.count('test_2'), 1) + self.assertEqual(bundle_list.count('description:'), 6) + + def testUpdateHelp(self): + '''Test the help for the update command''' + self.assertRaises(exceptions.SystemExit, + sdk_update.main, ['help', 'update']) + + def testUpdateBogusBundle(self): + '''Test running update with a bogus bundle''' + self.assertRaises(sdk_update.Error, + sdk_update.main, + ['update', 'bogusbundle']) + + def testSDKManifestFile(self): + '''Test SDKManifestFile''' + manifest_file = sdk_update.SDKManifestFile( + os.path.join(self._options.user_data_dir, + sdk_update.MANIFEST_FILENAME)) + self.assertNotEqual(None, manifest_file) + bundles = manifest_file.GetBundles() + self.assertEqual(3, len(bundles)) + test_bundle = manifest_file.GetBundleNamed('test_1') + self.assertNotEqual(None, test_bundle) + self.assertTrue('revision' in test_bundle) + self.assertEqual(1, test_bundle['revision']) + + def testNeedsUpdate(self): + '''Test that the test_1 bundle needs updating''' + tools = sdk_update.ManifestTools(self._options) + tools.LoadManifest() + bundles = tools.GetBundles() + self.assertEqual(3, len(bundles)) + local_manifest = sdk_update.SDKManifestFile( + os.path.join(self._options.user_data_dir, + sdk_update.MANIFEST_FILENAME)) + self.assertNotEqual(None, local_manifest) + for bundle in bundles: + bundle_name = bundle['name'] + self.assertTrue('revision' in bundle) + if bundle_name == 'test_1': + self.assertTrue(local_manifest.BundleNeedsUpdate(bundle)) + elif bundle_name == 'test_2': + self.assertTrue(local_manifest.BundleNeedsUpdate(bundle)) + else: + self.assertFalse(local_manifest.BundleNeedsUpdate(bundle)) + + def testMergeManifests(self): + '''Test merging a Bundle into a manifest file''' + tools = sdk_update.ManifestTools(self._options) + tools.LoadManifest() + bundles = tools.GetBundles() + self.assertEqual(3, len(bundles)) + local_manifest = sdk_update.SDKManifestFile( + os.path.join(self._options.user_data_dir, + sdk_update.MANIFEST_FILENAME)) + self.assertEqual(None, local_manifest.GetBundleNamed('test_2')) + for bundle in bundles: + local_manifest.MergeBundle(bundle) + self.assertNotEqual(None, local_manifest.GetBundleNamed('test_2')) + for bundle in bundles: + self.assertFalse(local_manifest.BundleNeedsUpdate(bundle)) + + def testMergeBundle(self): + '''Test MergeWithBundle''' + tools = sdk_update.ManifestTools(self._options) + tools.LoadManifest() + bundles = tools.GetBundles() + self.assertEqual(3, len(bundles)) + local_manifest = sdk_update.SDKManifestFile( + os.path.join(self._options.user_data_dir, + sdk_update.MANIFEST_FILENAME)) + self.assertNotEqual(None, local_manifest) + # Test the | operator. + for bundle in bundles: + bundle_name = bundle['name'] + if bundle_name == 'test_2': + continue + local_test_bundle = local_manifest.GetBundleNamed(bundle_name) + merged_bundle = local_test_bundle.MergeWithBundle(bundle) + self.assertTrue('revision' in merged_bundle) + if bundle_name == 'test_1': + self.assertEqual(2, merged_bundle['revision']) + else: + self.assertEqual(1, merged_bundle['revision']) + merged_bundle.Validate() + + def testVersion(self): + '''Test that showing the version works''' + self.assertRaises(exceptions.SystemExit, sdk_update.main, ['--version']) + +def main(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestSDKUpdate) + result = unittest.TextTestRunner(verbosity=2).run(suite) + + return int(not result.wasSuccessful()) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/native_client_sdk/src/build_tools/tests/set_nacl_env_test.py b/native_client_sdk/src/build_tools/tests/set_nacl_env_test.py new file mode 100755 index 0000000..e0a7d6f --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/set_nacl_env_test.py @@ -0,0 +1,129 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for set_nacl_env.py.""" + +import glob +import os +import shutil +import subprocess +import sys +import tempfile +import unittest + +from build_tools.sdk_tools import set_nacl_env + + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +SDK_ROOT_DIR = os.path.dirname(os.path.dirname(SCRIPT_DIR)) + + +class FakeOptions(object): + ''' Just a placeholder for options ''' + pass + + +class TestSetNaclEnv(unittest.TestCase): + ''' Test basic functionality of the set_nacl_env package ''' + def setUp(self): + self._options = FakeOptions() + self._options.host = 'mac' + self._options.lib_variant = 'newlib' + self._options.sdk_root = SDK_ROOT_DIR + self._options.sdk_platform = 'pepper_17' + self._options.build_type = 'debug' + self._options.no_ppapi = False + self._options.merge = False + + self._env = {} + self._env['NACL_SDK_ROOT'] = SDK_ROOT_DIR + + self._temp_dir = tempfile.mkdtemp(prefix='tmp_set_nacl_env_test') + + def tearDown(self): + shutil.rmtree(self._temp_dir, ignore_errors=True) + + def testBuildEnvX86_32(self): + ''' Test setting up a x86_32 build env ''' + self._options.arch = 'x86-32' + self._options.toolchain_path = set_nacl_env.GetToolchainPath(self._options) + env = set_nacl_env.SetupX86Env(self._options) + # Verify a few essential build options. + self.assertTrue('CFLAGS' in env) + self.assertTrue('CC' in env) + self.assertTrue('-m32' in env['CC']) + self.assertTrue('pepper_17' in env['CC']) + self.assertTrue('CXX' in env) + self.assertTrue('-m32' in env['CXX']) + + def testBuildEnvX86_64(self): + ''' Test setting up a x86_64 build env ''' + self._options.arch = 'x86-64' + self._options.toolchain_path = set_nacl_env.GetToolchainPath(self._options) + env = set_nacl_env.SetupX86Env(self._options) + # Verify a few essential build options. + self.assertTrue('CFLAGS' in env) + self.assertTrue('CC' in env) + self.assertTrue('-m64' in env['CC']) + self.assertTrue('pepper_17' in env['CC']) + self.assertTrue('CXX' in env) + self.assertTrue('-m64' in env['CXX']) + + def testBuildWithMake(self): + ''' Test building hello_world_c with make ''' + def MakeClean(): + ''' Invoke 'make clean' in the current directory and check the outcome. + ''' + cmd = [script, 'make clean --silent'] + self.assertEqual(0, subprocess.call(cmd, env=self._env, shell=False, + cwd=self._temp_dir)) + self.assertFalse(os.path.exists('hello_world_c.nexe')) + + # Can't use make on Windows + if sys.platform == 'win32': + return + + # The test directories are not generally writable. To be able to run make, + # we copy the files to a temp directory instead. + self.assertTrue(self._temp_dir) + src_dir = os.path.join(SCRIPT_DIR, 'set_nacl_env_test_archive') + for file in glob.iglob(os.path.join(src_dir, '*')): + shutil.copy2(file, self._temp_dir) + + script = os.path.join(SDK_ROOT_DIR, 'build_tools', 'sdk_tools', + 'set_nacl_env.py') + nexe_path = os.path.join(self._temp_dir, 'hello_world_c.nexe') + + # Build and verify the 32-bit version. + options = ['--platform=.', '--arch=x86-32'] + cmd = [script] + options + ['make hello_world_c.nexe'] + self.assertEqual(0, subprocess.call(cmd, env=self._env, shell=False, + cwd=self._temp_dir)) + self.assertTrue(os.path.exists(nexe_path)) + size_32 = os.path.getsize(nexe_path) + MakeClean() + + # Build and verify the 64-bit version. + options = ['--platform=.', '--arch=x86-64'] + cmd = [script] + options + ['make hello_world_c.nexe'] + self.assertEqual(0, subprocess.call(cmd, env=self._env, shell=False, + cwd=self._temp_dir)) + self.assertTrue(os.path.exists(nexe_path)) + size_64 = os.path.getsize(nexe_path) + MakeClean() + + # Verify that 64-bit version of nexe is larger than 32-bit version. + self.assertTrue(size_64 > size_32) + + +def main(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestSetNaclEnv) + result = unittest.TextTestRunner(verbosity=2).run(suite) + + return int(not result.wasSuccessful()) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/native_client_sdk/src/build_tools/tests/set_nacl_env_test_archive/Makefile b/native_client_sdk/src/build_tools/tests/set_nacl_env_test_archive/Makefile new file mode 100644 index 0000000..7db0ce7 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/set_nacl_env_test_archive/Makefile @@ -0,0 +1,20 @@ +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +EXAMPLE_DIR = . +EXAMPLE = hello_world_c.nexe + +all : $(EXAMPLE) + +clean : + rm -f $(EXAMPLE) hello_world_c.o hello_world_c.nexe + +SRCS_ = $(EXAMPLE_DIR)/*.c + +hello_world_c.o : $(SRCS_) + $(CXX) $(CPPFLAGS) -I$(EXAMPLE_DIR) $(CXXFLAGS) -c \ + $(EXAMPLE_DIR)/hello_world_c.c + +$(EXAMPLE) : hello_world_c.o + $(LINK) $(CFLAGS) $(LDFLAGS) $^ -o $@ diff --git a/native_client_sdk/src/build_tools/tests/set_nacl_env_test_archive/hello_world_c.c b/native_client_sdk/src/build_tools/tests/set_nacl_env_test_archive/hello_world_c.c new file mode 100644 index 0000000..939e724 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/set_nacl_env_test_archive/hello_world_c.c @@ -0,0 +1,287 @@ +/* Copyright (c) 2011 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** @file hello_world.c + * This example demonstrates loading, running and scripting a very simple + * NaCl module. + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/pp_module.h" +#include "ppapi/c/pp_var.h" +#include "ppapi/c/ppb.h" +#include "ppapi/c/ppb_instance.h" +#include "ppapi/c/ppb_messaging.h" +#include "ppapi/c/ppb_var.h" +#include "ppapi/c/ppp.h" +#include "ppapi/c/ppp_instance.h" +#include "ppapi/c/ppp_messaging.h" + +struct MessageInfo { + PP_Instance instance; + struct PP_Var message; +}; + +static const char* const kReverseTextMethodId = "reverseText"; +static const char* const kFortyTwoMethodId = "fortyTwo"; +static const char kMessageArgumentSeparator = ':'; +static const char kNullTerminator = '\0'; + +static struct PPB_Messaging* ppb_messaging_interface = NULL; +static struct PPB_Var* ppb_var_interface = NULL; +static PP_Module module_id = 0; + + +/** + * Returns a mutable C string contained in the @a var or NULL if @a var is not + * string. This makes a copy of the string in the @ var and adds a NULL + * terminator. Note that VarToUtf8() does not guarantee the NULL terminator on + * the returned string. See the comments for VatToUtf8() in ppapi/c/ppb_var.h + * for more info. The caller is responsible for freeing the returned memory. + * @param[in] var PP_Var containing string. + * @return a C string representation of @a var. + * @note The caller is responsible for freeing the returned string. + */ +static char* VarToCStr(struct PP_Var var) { + uint32_t len = 0; + if (ppb_var_interface != NULL) { + const char* var_c_str = ppb_var_interface->VarToUtf8(var, &len); + if (len > 0) { + char* c_str = (char*)malloc(len + 1); + memcpy(c_str, var_c_str, len); + c_str[len] = kNullTerminator; + return c_str; + } + } + return NULL; +} + +/** + * Creates new string PP_Var from C string. The resulting object will be a + * refcounted string object. It will be AddRef()ed for the caller. When the + * caller is done with it, it should be Release()d. + * @param[in] str C string to be converted to PP_Var + * @return PP_Var containing string. + */ +static struct PP_Var CStrToVar(const char* str) { + if (ppb_var_interface != NULL) { + return ppb_var_interface->VarFromUtf8(module_id, str, strlen(str)); + } + return PP_MakeUndefined(); +} + +/** + * Reverse C string in-place. + * @param[in,out] str C string to be reversed + */ +static void ReverseStr(char* str) { + char* right = str + strlen(str) - 1; + char* left = str; + while (left < right) { + char tmp = *left; + *left++ = *right; + *right-- = tmp; + } +} + +/** + * A simple function that always returns 42. + * @return always returns the integer 42 + */ +static struct PP_Var FortyTwo() { + return PP_MakeInt32(42); +} + +/** + * Called when the NaCl module is instantiated on the web page. The identifier + * of the new instance will be passed in as the first argument (this value is + * generated by the browser and is an opaque handle). This is called for each + * instantiation of the NaCl module, which is each time the <embed> tag for + * this module is encountered. + * + * If this function reports a failure (by returning @a PP_FALSE), the NaCl + * module will be deleted and DidDestroy will be called. + * @param[in] instance The identifier of the new instance representing this + * NaCl module. + * @param[in] argc The number of arguments contained in @a argn and @a argv. + * @param[in] argn An array of argument names. These argument names are + * supplied in the <embed> tag, for example: + * <embed id="nacl_module" dimensions="2"> + * will produce two arguments, one named "id" and one named "dimensions". + * @param[in] argv An array of argument values. These are the values of the + * arguments listed in the <embed> tag. In the above example, there will + * be two elements in this array, "nacl_module" and "2". The indices of + * these values match the indices of the corresponding names in @a argn. + * @return @a PP_TRUE on success. + */ +static PP_Bool Instance_DidCreate(PP_Instance instance, + uint32_t argc, + const char* argn[], + const char* argv[]) { + return PP_TRUE; +} + +/** + * Called when the NaCl module is destroyed. This will always be called, + * even if DidCreate returned failure. This routine should deallocate any data + * associated with the instance. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + */ +static void Instance_DidDestroy(PP_Instance instance) { +} + +/** + * Called when the position, the size, or the clip rect of the element in the + * browser that corresponds to this NaCl module has changed. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] position The location on the page of this NaCl module. This is + * relative to the top left corner of the viewport, which changes as the + * page is scrolled. + * @param[in] clip The visible region of the NaCl module. This is relative to + * the top left of the plugin's coordinate system (not the page). If the + * plugin is invisible, @a clip will be (0, 0, 0, 0). + */ +static void Instance_DidChangeView(PP_Instance instance, + const struct PP_Rect* position, + const struct PP_Rect* clip) { +} + +/** + * Notification that the given NaCl module has gained or lost focus. + * Having focus means that keyboard events will be sent to the NaCl module + * represented by @a instance. A NaCl module's default condition is that it + * will not have focus. + * + * Note: clicks on NaCl modules will give focus only if you handle the + * click event. You signal if you handled it by returning @a true from + * HandleInputEvent. Otherwise the browser will bubble the event and give + * focus to the element on the page that actually did end up consuming it. + * If you're not getting focus, check to make sure you're returning true from + * the mouse click in HandleInputEvent. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] has_focus Indicates whether this NaCl module gained or lost + * event focus. + */ +static void Instance_DidChangeFocus(PP_Instance instance, + PP_Bool has_focus) { +} + +/** + * Handler that gets called after a full-frame module is instantiated based on + * registered MIME types. This function is not called on NaCl modules. This + * function is essentially a place-holder for the required function pointer in + * the PPP_Instance structure. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] url_loader A PP_Resource an open PPB_URLLoader instance. + * @return PP_FALSE. + */ +static PP_Bool Instance_HandleDocumentLoad(PP_Instance instance, + PP_Resource url_loader) { + /* NaCl modules do not need to handle the document load function. */ + return PP_FALSE; +} + +/** + * Handler for messages coming in from the browser via postMessage. Extracts + * the method call from @a message, parses it for method name and value, then + * calls the appropriate function. In the case of the reverseString method, the + * message format is a simple colon-separated string. The first part of the + * string up to the colon is the method name; after that is the string argument. + * @param[in] instance The instance ID. + * @param[in] message The contents, copied by value, of the message sent from + * browser via postMessage. + */ +void Messaging_HandleMessage(PP_Instance instance, struct PP_Var var_message) { + if (var_message.type != PP_VARTYPE_STRING) { + /* Only handle string messages */ + return; + } + char* message = VarToCStr(var_message); + if (message == NULL) + return; + struct PP_Var var_result = PP_MakeUndefined(); + if (strncmp(message, kFortyTwoMethodId, strlen(kFortyTwoMethodId)) == 0) { + var_result = FortyTwo(); + } else if (strncmp(message, + kReverseTextMethodId, + strlen(kReverseTextMethodId)) == 0) { + /* Use everything after the ':' in |message| as the string argument. */ + char* string_arg = strchr(message, kMessageArgumentSeparator); + if (string_arg != NULL) { + string_arg += 1; /* Advance past the ':' separator. */ + ReverseStr(string_arg); + var_result = CStrToVar(string_arg); + } + } + free(message); + + /* Echo the return result back to browser. Note that HandleMessage is always + * called on the main thread, so it's OK to post the message back to the + * browser directly from here. This return post is asynchronous. + */ + ppb_messaging_interface->PostMessage(instance, var_result); + /* If the message was created using VarFromUtf8() it needs to be released. + * See the comments about VarFromUtf8() in ppapi/c/ppb_var.h for more + * information. + */ + if (var_result.type == PP_VARTYPE_STRING) { + ppb_var_interface->Release(var_result); + } +} + +/** + * Entry points for the module. + * Initialize needed interfaces: PPB_Core, PPB_Messaging and PPB_Var. + * @param[in] a_module_id module ID + * @param[in] get_browser pointer to PPB_GetInterface + * @return PP_OK on success, any other value on failure. + */ +PP_EXPORT int32_t PPP_InitializeModule(PP_Module a_module_id, + PPB_GetInterface get_browser) { + module_id = a_module_id; + ppb_messaging_interface = + (struct PPB_Messaging*)(get_browser(PPB_MESSAGING_INTERFACE)); + ppb_var_interface = (struct PPB_Var*)(get_browser(PPB_VAR_INTERFACE)); + + return PP_OK; +} + +/** + * Returns an interface pointer for the interface of the given name, or NULL + * if the interface is not supported. + * @param[in] interface_name name of the interface + * @return pointer to the interface + */ +PP_EXPORT const void* PPP_GetInterface(const char* interface_name) { + if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0) { + static struct PPP_Instance instance_interface = { + &Instance_DidCreate, + &Instance_DidDestroy, + &Instance_DidChangeView, + &Instance_DidChangeFocus, + &Instance_HandleDocumentLoad, + }; + return &instance_interface; + } else if (strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0) { + static struct PPP_Messaging messaging_interface = { + &Messaging_HandleMessage + }; + return &messaging_interface; + } + return NULL; +} + +/** + * Called before the plugin module is unloaded. + */ +PP_EXPORT void PPP_ShutdownModule() { +} diff --git a/native_client_sdk/src/build_tools/tests/tar_archive_test.py b/native_client_sdk/src/build_tools/tests/tar_archive_test.py new file mode 100644 index 0000000..d16c193 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/tar_archive_test.py @@ -0,0 +1,200 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for tar_archive.py.""" + +import os +import sys +import tarfile +import unittest + +from build_tools import tar_archive + + +class TestTarArchive(unittest.TestCase): + """This class tests basic functionality of the tar_archive package""" + + def setUp(self): + self.archive = tar_archive.TarArchive() + + def ValidateTableOfContents(self, archive, path_filter=None): + '''Helper method to validate an archive table of contents. + + The test table of contents file has these entries (from tar tv): + drwxr-xr-x test_links/ + drwxr-xr-x test_links/test_dir/ + lrwxr-xr-x test_links/test_dir_slnk -> test_dir + -rw-r--r-- test_links/test_hlnk_file_dst1.txt + hrw-r--r-- test_links/test_hlnk_file_dst2.txt link to \ + test_links/test_hlnk_file_dst1.txt + hrw-r--r-- test_links/test_hlnk_file_src.txt link to \ + test_links/test_hlnk_file_dst1.txt + lrwxr-xr-x test_links/test_slnk_file_dst.txt -> test_slnk_file_src.txt + -rw-r--r-- test_links/test_slnk_file_src.txt + -rw-r--r-- test_links/test_dir/test_file.txt + hrw-r--r-- test_links/test_dir/test_hlnk_file_dst3.txt \ + test_links/test_hlnk_file_dst1.txt + lrwxr-xr-x test_links/test_dir/test_slnk_file_dst2.txt -> \ + ../test_slnk_file_src.txt + + Args: + archive: The TarArchive object under test. + ''' + if not path_filter: + path_filter = lambda p: os.path.join('test_links', p) + + self.assertEqual(3, len(archive.files)) + self.assertTrue(path_filter('test_slnk_file_src.txt') in archive.files) + self.assertTrue(path_filter(os.path.join('test_dir', 'test_file.txt')) in + archive.files) + for file in archive.files: + self.assertFalse('_dir' in os.path.basename(file)) + self.assertTrue(path_filter(os.path.join('test_dir', 'test_file.txt')) in + archive.files) + + self.assertEqual(2, len(archive.dirs)) + self.assertTrue(path_filter('test_dir') in archive.dirs) + for dir in archive.dirs: + self.assertFalse('slnk' in dir) + self.assertFalse('.txt' in dir) + self.assertFalse(dir in archive.files) + self.assertFalse(dir in archive.symlinks.keys()) + + self.assertEqual(3, len(archive.symlinks)) + self.assertTrue(path_filter('test_dir_slnk') in archive.symlinks) + self.assertTrue(path_filter('test_slnk_file_dst.txt') in archive.symlinks) + for path, target in archive.symlinks.items(): + self.assertFalse(path in archive.files) + self.assertFalse(path in archive.dirs) + # Make sure the target exists in either |archive.files| or + # |archive.dirs|. The target path in the archive is relative to the + #source file's path. + target_path = os.path.normpath(os.path.join( + os.path.dirname(path), target)) + self.assertTrue((target_path in archive.files) or + (target_path in archive.dirs)) + + self.assertEqual(3, len(archive.links)) + # There is no "source" file for hard links like there is for a symbolic + # link, so there it's possible that the hlnk_src file is in the + # |archive.links| set, which is OK as long as one of the hlnk files is + # in the |archive.files| set. Make sure that only hlnk files are in the + # |archive.links| list. + for path, target in archive.links.items(): + self.assertTrue('test_hlnk_file' in path) + self.assertFalse(path in archive.files) + self.assertFalse(path in archive.dirs) + self.assertTrue(target in archive.files) + self.assertFalse(target in archive.dirs) + + def testConstructor(self): + """Test default constructor.""" + self.assertEqual(0, len(self.archive.files)) + self.assertEqual(0, len(self.archive.dirs)) + self.assertEqual(0, len(self.archive.symlinks)) + self.assertEqual(0, len(self.archive.links)) + + def testFromTarball(self): + """Testing the TarArchive when using a tarball""" + # Use a known test archive to validate the TOC entries. + self.archive.InitWithTarFile(os.path.join('build_tools', + 'tests', + 'test_links.tgz')) + self.ValidateTableOfContents(self.archive) + + def testFromTarballBadFile(self): + """Testing the TarArchive when using a bad tarball""" + self.assertRaises(OSError, + self.archive.InitWithTarFile, + 'nosuchfile') + self.assertRaises(tarfile.ReadError, + self.archive.InitWithTarFile, + os.path.join('build_tools', + 'tests', + 'test_links.tgz.manifest')) + + def testFromManifest(self): + """Testing the TarArchive when using a manifest file""" + # Use a known test manifest to validate the TOC entries. + # The test manifest file is the output of tar -tv on the tarball used in + # testGetArchiveTableOfContents(). + self.archive.InitWithManifest(os.path.join('build_tools', + 'tests', + 'test_links.tgz.manifest')) + self.ValidateTableOfContents(self.archive) + + def testFromManifestBadFile(self): + """Testing the TarArchive when using a bad manifest file""" + self.assertRaises(OSError, self.archive.InitWithManifest, 'nosuchfile') + + def testPathFilter(self): + """Testing the TarArchive when applying a path filter""" + def StripTestLinks(tar_path): + # Strip off the leading 'test_links/' path component. + pos = tar_path.find('test_links/') + if pos >= 0: + return os.path.normpath(tar_path[len('test_links/'):]) + else: + return os.path.normpath(tar_path) + + self.archive.path_filter = StripTestLinks + self.archive.InitWithTarFile(os.path.join('build_tools', + 'tests', + 'test_links.tgz')) + self.ValidateTableOfContents(self.archive, path_filter=lambda p: p) + self.archive.InitWithManifest(os.path.join('build_tools', + 'tests', + 'test_links.tgz.manifest')) + self.ValidateTableOfContents(self.archive, path_filter=lambda p: p) + + def testPathFilterNone(self): + """Testing the TarArchive when applying a None path filter""" + # Verify that the paths in the |archive| object have the tar-style '/' + # separator. + def ValidateTarStylePaths(archive): + def AssertTarPath(iterable): + for i in iterable: + self.assertTrue(len(i.split('/')) > 0) + + self.assertEqual(3, len(archive.files)) + AssertTarPath(archive.files) + self.assertEqual(2, len(archive.dirs)) + AssertTarPath(archive.dirs) + self.assertEqual(3, len(archive.symlinks)) + AssertTarPath(archive.symlinks.keys()) + self.assertEqual(3, len(archive.links)) + AssertTarPath(archive.links.keys()) + + self.archive.path_filter = None + self.archive.InitWithTarFile(os.path.join('build_tools', + 'tests', + 'test_links.tgz')) + ValidateTarStylePaths(self.archive) + self.archive.InitWithManifest(os.path.join('build_tools', + 'tests', + 'test_links.tgz.manifest')) + ValidateTarStylePaths(self.archive) + + def testDeletePathFilter(self): + # Note: due to the use of del() here, self.assertRaises() can't be used. + # Also, the with self.assertRaises() idiom is not in python 2.6 so it + # can't be used either. + try: + del(self.archive.path_filter) + except tar_archive.Error: + pass + else: + raise + +def RunTests(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestTarArchive) + result = unittest.TextTestRunner(verbosity=2).run(suite) + + return int(not result.wasSuccessful()) + + +if __name__ == '__main__': + sys.exit(RunTests()) diff --git a/native_client_sdk/src/build_tools/tests/test_links.tgz b/native_client_sdk/src/build_tools/tests/test_links.tgz Binary files differnew file mode 100644 index 0000000..898f147 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/test_links.tgz diff --git a/native_client_sdk/src/build_tools/tests/test_links.tgz.manifest b/native_client_sdk/src/build_tools/tests/test_links.tgz.manifest new file mode 100644 index 0000000..dff6fbc2 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/test_links.tgz.manifest @@ -0,0 +1,11 @@ +drwxr-xr-x 0 user staff 0 Aug 30 14:53 test_links/ +drwxr-xr-x 0 user staff 0 Sep 6 16:30 test_links/test_dir/ +lrwxr-xr-x 0 user staff 0 Aug 30 14:53 test_links/test_dir_slnk -> test_dir +-rw-r--r-- 0 user staff 35 Aug 30 14:53 test_links/test_hlnk_file_dst1.txt +hrw-r--r-- 0 user staff 0 Aug 30 14:53 test_links/test_hlnk_file_dst2.txt link to test_links/test_hlnk_file_dst1.txt +hrw-r--r-- 0 user staff 0 Aug 30 14:53 test_links/test_hlnk_file_src.txt link to test_links/test_hlnk_file_dst1.txt +lrwxr-xr-x 0 user staff 0 Aug 30 14:52 test_links/test_slnk_file_dst.txt -> test_slnk_file_src.txt +-rw-r--r-- 0 user staff 33 Aug 30 14:51 test_links/test_slnk_file_src.txt +-rw-r--r-- 0 user staff 38 Aug 30 14:52 test_links/test_dir/test_file.txt +hrw-r--r-- 0 user staff 0 Aug 30 14:53 test_links/test_dir/test_hlnk_file_dst3.txt link to test_links/test_hlnk_file_dst1.txt +lrwxr-xr-x 0 user staff 0 Sep 6 16:29 test_links/test_dir/test_slnk_file_dst2.txt -> ../test_slnk_file_src.txt diff --git a/native_client_sdk/src/build_tools/tests/test_sdk_section.nsh b/native_client_sdk/src/build_tools/tests/test_sdk_section.nsh new file mode 100644 index 0000000..ac78aa7 --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/test_sdk_section.nsh @@ -0,0 +1,8 @@ +Section "!Native Client SDK" NativeClientSDK + SectionIn RO + SetOutPath $INSTDIR + CreateDirectory "$INSTDIR\test_dir" + File "/oname=test_file.txt" "build_tools\tests\nsis_test_archive\test_file.txt" + File "/oname=test_dir\test_dir_file1.txt" "build_tools\tests\nsis_test_archive\test_dir\test_dir_file1.txt" + File "/oname=test_dir\test_dir_file2.txt" "build_tools\tests\nsis_test_archive\test_dir\test_dir_file2.txt" +SectionEnd diff --git a/native_client_sdk/src/build_tools/tests/update_manifest_test.py b/native_client_sdk/src/build_tools/tests/update_manifest_test.py new file mode 100755 index 0000000..ce2c89a --- /dev/null +++ b/native_client_sdk/src/build_tools/tests/update_manifest_test.py @@ -0,0 +1,341 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for update_manifest.py.""" + +__author__ = 'mball@google.com (Matt Ball)' + +import errno +import os +import SimpleHTTPServer +import SocketServer +import sys +import tempfile +import threading +import unittest +import urlparse + +from build_tools.sdk_tools import sdk_update +from build_tools.sdk_tools import update_manifest + +TEST_DIR = os.path.dirname(os.path.abspath(__file__)) + + +def RemoveFile(filename): + '''Remove a filename if it exists and do nothing if it doesn't exist''' + try: + os.remove(filename) + except OSError as error: + if error.errno != errno.ENOENT: + raise + + +def GetHTTPHandler(path, length=None): + '''Returns a simple HTTP Request Handler that only servers up a given file + + Args: + path: path and filename of the file to serve up + length: (optional) only serve up the first |length| bytes + + Returns: + A SimpleHTTPRequestHandler class''' + class HTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def do_GET(self): + with open(path, 'rb') as f: + # This code is largely lifted from SimpleHTTPRequestHandler.send_head + self.send_response(200) + self.send_header("Content-type", self.guess_type(path)) + fs = os.fstat(f.fileno()) + self.send_header("Content-Length", str(fs[6])) + self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) + self.end_headers() + if length != None: + self.wfile.write(f.read(length)) + else: + self.copyfile(f, self.wfile) + return HTTPHandler + + +class FakeOptions(object): + ''' Just a place holder for options ''' + def __init__(self): + self.bundle_desc_url = None + self.bundle_name = None + self.bundle_version = None + self.bundle_revision = None + self.desc = None + self.gsutil = os.path.join(TEST_DIR, 'fake_gsutil.bat' + if sys.platform == 'win32' else 'fake_gsutil.py') + self.linux_arch_url = None + self.mac_arch_url = None + self.manifest_file = os.path.join(TEST_DIR, 'naclsdk_manifest_test.json') + self.manifest_version = None + self.recommended = None + self.root_url = 'http://localhost/test_url' + self.stability = None + self.upload = False + self.win_arch_url = None + + +class TestUpdateManifest(unittest.TestCase): + ''' Test basic functionality of the update_manifest package. + + Note that update_manifest.py now imports sdk_update.py, so this file + tests the update_manifest features within sdk_update.''' + + def setUp(self): + self._json_boilerplate=( + '{\n' + ' "bundles": [],\n' + ' "manifest_version": 1\n' + '}\n') + self._temp_dir = tempfile.gettempdir() + # os.path.join('build_tools', 'tests', 'test_archive') + self._manifest = update_manifest.UpdateSDKManifest() + + def testJSONBoilerplate(self): + ''' Test creating a manifest object''' + self.assertEqual(self._manifest.GetManifestString(), + self._json_boilerplate) + # Test using a manifest file with a version that is too high + self.assertRaises(sdk_update.Error, + self._manifest.LoadManifestString, + '{"manifest_version": 2}') + + def testWriteLoadManifestFile(self): + ''' Test writing to and loading from a manifest file''' + # Remove old test file + file_path = os.path.join(self._temp_dir, 'temp_manifest.json') + if os.path.exists(file_path): + os.remove(file_path) + # Create a basic manifest file + manifest_file = sdk_update.SDKManifestFile(file_path) + manifest_file.WriteFile(); + self.assertTrue(os.path.exists(file_path)) + # Test re-loading the file + manifest_file._manifest._manifest_data['manifest_version'] = 0 + manifest_file._LoadFile() + self.assertEqual(manifest_file._manifest.GetManifestString(), + self._json_boilerplate) + os.remove(file_path) + + def testValidateBundleName(self): + ''' Test validating good and bad bundle names ''' + self.assertTrue( + self._manifest._ValidateBundleName('A_Valid.Bundle-Name(1)')) + self.assertFalse(self._manifest._ValidateBundleName('A bad name')) + self.assertFalse(self._manifest._ValidateBundleName('A bad/name')) + self.assertFalse(self._manifest._ValidateBundleName('A bad;name')) + self.assertFalse(self._manifest._ValidateBundleName('A bad,name')) + + def testUpdateManifestVersion(self): + ''' Test updating the manifest version number ''' + options = FakeOptions() + options.manifest_version = 99 + self.assertEqual(self._manifest._manifest_data['manifest_version'], 1) + self._manifest._UpdateManifestVersion(options) + self.assertEqual(self._manifest._manifest_data['manifest_version'], 99) + + def testVerifyAllOptionsConsumed(self): + ''' Test function _VerifyAllOptionsConsumed ''' + options = FakeOptions() + options.opt1 = None + self.assertTrue(self._manifest._VerifyAllOptionsConsumed(options, None)) + options.opt2 = 'blah' + self.assertRaises(update_manifest.Error, + self._manifest._VerifyAllOptionsConsumed, + options, + 'no bundle name') + + def testBundleUpdate(self): + ''' Test function Bundle.Update ''' + bundle = sdk_update.Bundle('test') + options = FakeOptions() + options.bundle_revision = 1 + options.bundle_version = 2 + options.desc = 'What a hoot' + options.stability = 'dev' + options.recommended = 'yes' + update_manifest.UpdateBundle(bundle, options) + self.assertEqual(bundle['revision'], 1) + + def testUpdateManifestModifyTopLevel(self): + ''' Test function UpdateManifest: modifying top-level info ''' + options = FakeOptions() + options.manifest_version = 0 + options.bundle_name = None + self._manifest.UpdateManifest(options) + self.assertEqual(self._manifest._manifest_data['manifest_version'], 0) + + def testUpdateManifestModifyBundle(self): + ''' Test function UpdateManifest: adding/modifying a bundle ''' + # Add a bundle + options = FakeOptions() + options.manifest_version = 1 + options.bundle_name = 'test' + options.bundle_revision = 2 + options.bundle_version = 3 + options.desc = 'nice bundle' + options.stability = 'canary' + options.recommended = 'yes' + self._manifest.UpdateManifest(options) + bundle = self._manifest.GetBundle('test') + self.assertNotEqual(bundle, None) + # Modify the same bundle + options = FakeOptions() + options.manifest_version = None + options.bundle_name = 'test' + options.desc = 'changed' + self._manifest.UpdateManifest(options) + bundle = self._manifest.GetBundle('test') + self.assertEqual(bundle['description'], 'changed') + + def testUpdateManifestBadBundle1(self): + ''' Test function UpdateManifest: bad bundle data ''' + options = FakeOptions() + options.manifest_version = None + options.bundle_name = 'test' + options.stability = 'excellent' + self.assertRaises(sdk_update.Error, + self._manifest.UpdateManifest, + options) + + def testUpdateManifestBadBundle2(self): + ''' Test function UpdateManifest: incomplete bundle data ''' + options = FakeOptions() + options.manifest_version = None + options.bundle_name = 'another_bundle' + self.assertRaises(sdk_update.Error, + self._manifest.UpdateManifest, + options) + + def testUpdateManifestArchiveComputeSha1AndSize(self): + ''' Test function Archive.Update ''' + temp_file_path = None + try: + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + # Create a temp file with some data + temp_file.write(r'abcdefghijklmnopqrstuvwxyz0123456789') + temp_file_path = temp_file.name + # Windows requires that we close the file before reading from it. + temp_file.close() + + # Create an archive with a url to the file we created above. + url_parts = urlparse.ParseResult('file', '', temp_file_path, '', '', '') + url = urlparse.urlunparse(url_parts) + archive = sdk_update.Archive('mac') + archive.Update(url) + self.assertEqual(archive['checksum']['sha1'], + 'd2985049a677bbc4b4e8dea3b89c4820e5668e3a') + finally: + if temp_file_path and os.path.exists(temp_file_path): + os.remove(temp_file_path) + + def testUpdateManifestArchiveValidate(self): + ''' Test function Archive.Validate ''' + # Test invalid host-os name + archive = sdk_update.Archive('atari') + self.assertRaises(sdk_update.Error, archive.Validate) + # Test missing url + archive['host_os'] = 'mac' + self.assertRaises(sdk_update.Error, archive.Validate) + # Valid archive + archive['url'] = 'http://www.google.com' + archive.Validate() + # Test invalid key name + archive['guess'] = 'who' + self.assertRaises(sdk_update.Error, archive.Validate) + + def testUpdatePartialFile(self): + '''Test updating with a partially downloaded file''' + server = None + server_thread = None + temp_filename = os.path.join(self._temp_dir, + 'testUpdatePartialFile_temp.txt') + try: + # Create a new local server on an arbitrary port that just serves-up + # the first 10 bytes of this file. + server = SocketServer.TCPServer( + ("", 0), GetHTTPHandler(__file__, 10)) + ip, port = server.server_address + server_thread = threading.Thread(target=server.serve_forever) + server_thread.start() + + archive = sdk_update.Archive('mac') + self.assertRaises(sdk_update.Error, + archive.Update, + 'http://localhost:%s' % port) + try: + self.assertRaises(sdk_update.Error, + archive.DownloadToFile, + temp_filename) + finally: + RemoveFile(temp_filename) + finally: + if server_thread and server_thread.isAlive(): + server.shutdown() + server_thread.join() + + def testUpdateManifestMain(self): + ''' test the main function from update_manifest ''' + temp_filename = os.path.join(self._temp_dir, 'testUpdateManifestMain.json') + try: + argv = ['--bundle-version', '0', + '--bundle-revision', '0', + '--description', 'test bundle for update_manifest unit tests', + '--bundle-name', 'test_bundle', + '--stability', 'dev', + '--recommended', 'no', + '--manifest-file', temp_filename] + update_manifest.main(argv) + finally: + RemoveFile(temp_filename) + + def testHandleSDKTools(self): + '''Test the handling of the sdk_tools bundle''' + options = FakeOptions() + options.bundle_name = 'sdk_tools' + options.upload = True + options.bundle_version = 0 + self.assertRaises( + update_manifest.Error, + update_manifest.UpdateSDKManifestFile(options).HandleBundles) + options.bundle_version = None + options.bundle_revision = 0 + self.assertRaises( + update_manifest.Error, + update_manifest.UpdateSDKManifestFile(options).HandleBundles) + options.bundle_revision = None + update_manifest.UpdateSDKManifestFile(options).HandleBundles() + + def testHandlePepper(self): + '''Test the handling of pepper bundles''' + options = FakeOptions() + options.bundle_name = 'pepper' + options.bundle_version = None + self.assertRaises( + update_manifest.Error, + update_manifest.UpdateSDKManifestFile(options).HandleBundles) + options.bundle_version = 1 + options.bundle_revision = None + self.assertRaises( + update_manifest.Error, + update_manifest.UpdateSDKManifestFile(options).HandleBundles) + options.bundle_revision = 0 + update_manifest.UpdateSDKManifestFile(options).HandleBundles() + options.bundle_name = 'pepper_1' + options.bundle_version = None + update_manifest.UpdateSDKManifestFile(options).HandleBundles() + + +def main(): + suite = unittest.TestLoader().loadTestsFromTestCase(TestUpdateManifest) + result = unittest.TextTestRunner(verbosity=2).run(suite) + + return int(not result.wasSuccessful()) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/native_client_sdk/src/codereview.settings b/native_client_sdk/src/codereview.settings new file mode 100644 index 0000000..d1405a3 --- /dev/null +++ b/native_client_sdk/src/codereview.settings @@ -0,0 +1,9 @@ +# This file is used by gcl to get repository specific information. +CODE_REVIEW_SERVER: codereview.chromium.org +CC_LIST: native-client-reviews@googlegroups.com +VIEW_VC: http://code.google.com/p/nativeclient-sdk/source/detail?r= +STATUS: http://naclsdk-status.appspot.com/status +TRY_ON_UPLOAD: True +TRYSERVER_PROJECT: naclsdk +TRYSERVER_SVN_URL: svn://svn.chromium.org/chrome-try/try-nacl +DEPTH: 0 diff --git a/native_client_sdk/src/documentation/Doxyfile b/native_client_sdk/src/documentation/Doxyfile new file mode 100644 index 0000000..54b8213 --- /dev/null +++ b/native_client_sdk/src/documentation/Doxyfile @@ -0,0 +1,1663 @@ +# Doxyfile 1.7.2 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = "c_salt" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = scons-out/documentation + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = YES + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command <command> <input-file>, where <command> is the value of +# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ./c_salt \ + ./examples \ + ./documentation + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.vhd *.vhdl + +FILE_PATTERNS = *.h \ + *.dox + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = _*.h + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = ./documentation/images-dox + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command <filter> <input-file>, where <filter> +# is the value of the INPUT_FILTER tag, and <input-file> is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = documentation/header.dox + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = documentation/footer.dox + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = documentation/stylesheet-dox.css + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the stylesheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 30 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters"> +# Qt Help Project / Custom Filters</a>. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes"> +# Qt Help Project / Filter Attributes</a>. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [0,1..20]) +# that doxygen will group on one line in the generated HTML documentation. +# Note that a value of 0 will completely suppress the enum values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 251 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the mathjax.org site, so you can quickly see the result without installing +# MathJax, but it is strongly recommended to install a local copy of MathJax +# before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = YES + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = __native_client__ \ + DOXYGEN_SHOULD_SKIP_THIS \ + __attribute__(x)= \ + EXTERN_C_BEGIN= \ + EXTERN_C_END= + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans.ttf + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = YES + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif. +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 10 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/native_client_sdk/src/documentation/build.scons b/native_client_sdk/src/documentation/build.scons new file mode 100644 index 0000000..1b6dd43 --- /dev/null +++ b/native_client_sdk/src/documentation/build.scons @@ -0,0 +1,28 @@ +#! -*- python -*- +# +# Copyright (c) 2010 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""scons script for building the Doxygen-based documentation for c_salt""" + +Import('env') + +env.SetDefault(DOXYGEN=ARGUMENTS.get('DOXYGEN', 'doxygen')) + +# Gate on host platform rather than target, since all are supported. +if env['PLATFORM'] in ['win32', 'cygwin']: + env['ENV']['NACL_SDK_PLATFORM'] = 'windows' +elif env['PLATFORM'] in ['darwin']: + env['ENV']['NACL_SDK_PLATFORM'] = 'mac' +else: + env['ENV']['NACL_SDK_PLATFORM'] = 'linux' + +# The output is generated into scons-out/doc (c.f. Doxyfile). +# Point your browser at scons-out/doc/html/index.html +node = env.Command( + target='doxygen.log', + source='Doxyfile', + action='${DOXYGEN} documentation/Doxyfile 2>&1 > ${TARGET}') +AlwaysBuild(node) +env.AddNodeAliases(node, [], 'docs') diff --git a/native_client_sdk/src/documentation/check.sh b/native_client_sdk/src/documentation/check.sh new file mode 100755 index 0000000..ce3b49f --- /dev/null +++ b/native_client_sdk/src/documentation/check.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# simple script to check html via tidy. Either specify html files on +# command line or rely on default which checks all html files in +# current directory +set -o nounset +set -o errexit + + +CheckFile () { + echo "========================================" + echo "checking $1" + echo "========================================" + tidy -e -q $1 +} + + +if [ $# -eq 0 ] ; then + for file in *.html ; do + CheckFile ${file} + done +else + for file in $* ; do + CheckFile ${file} + done +fi diff --git a/native_client_sdk/src/documentation/footer.dox b/native_client_sdk/src/documentation/footer.dox new file mode 100644 index 0000000..8a944c1 --- /dev/null +++ b/native_client_sdk/src/documentation/footer.dox @@ -0,0 +1,22 @@ + <p id="license"> + Except as otherwise + <a href="http://code.google.com/policies.html#restrictions">noted</a>, + the content of this page is licensed under a + <a href="http://www.google.com/url?sa=D&q=http%3A%2F%2Fcreativecommons.org/licenses/by/2.5/">Creative Commons + Attribution 2.5 license</a>. + </p> + + <p align="center"><b>Warning: This API is + currently in development and is subject to change.</b></p> + + <address> + ©2010 Google + </address> + + <address> + Generated $date by + <a href="http://www.doxygen.org/index.html">doxygen</a> $doxygenversion + </address> + + </body> +</html> diff --git a/native_client_sdk/src/documentation/header.dox b/native_client_sdk/src/documentation/header.dox new file mode 100644 index 0000000..a261dd3 --- /dev/null +++ b/native_client_sdk/src/documentation/header.dox @@ -0,0 +1,14 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> + <title>$title</title> + <link href="./tabs.css" rel="stylesheet" type="text/css"> + <link href="./stylesheet-dox.css" rel="stylesheet" type="text/css"> + <link href="./stylesheet-dox-all.css" rel="stylesheet" type="text/css"> + </head> + <body> + <div id="toplinks"> + <a href="http://code.google.com/p/nativeclient-sdk/"> + Native Client SDK homepage</a> + </div> diff --git a/native_client_sdk/src/documentation/images-dox/README.txt b/native_client_sdk/src/documentation/images-dox/README.txt new file mode 100644 index 0000000..9a9e18c --- /dev/null +++ b/native_client_sdk/src/documentation/images-dox/README.txt @@ -0,0 +1,9 @@ +This directory holds all images that go into the doxygen-generated +API reference doc. To include an image, use code like the following: + +@image html figure.jpg + +If you want a caption, specify it like this: + +@image html figure.jpg "Test image" + diff --git a/native_client_sdk/src/documentation/index.dox b/native_client_sdk/src/documentation/index.dox new file mode 100644 index 0000000..2af602e --- /dev/null +++ b/native_client_sdk/src/documentation/index.dox @@ -0,0 +1,46 @@ +/** @mainpage c_salt Reference Documentation + + This reference documentation describes the c_salt API, which is + an open-source API for browser plugins that layers on + top of the Pepper API and the NPAPI. + You can use the c_salt API + in <a href="http://code.google.com/p/nativeclient-sdk">Native Client</a> + modules to communicate with the Google Chrome browser. + This page has the following contents: + + - @ref reading + - @ref modules + - @ref about + + + @section reading Before you start + + This documentation assumes that you have read and understood + the following pages in the Native Client SDK project: + + - <a href="http://code.google.com/p/nativeclient-sdk/">Getting started</a> + + + @section modules API categories + +The c_salt API consists of a C++ API in the c_salt namespace. + +(Details TBD)... + + + @section about About this doc + + <p> + The tabs at the top of each page take you to the following sections. + </p> + + - <b>Main Page</b>: This page + - <a href="modules.html"><b>Modules</b></a>: Lets you find API by functional area + - <a href="annotated.html"><b>Data Structures</b></a>: + List of classes and data structures in c_salt. + - <a href="files.html"><b>Files</b></a>: + The header files used to generate this documentation, + with file descriptions and links to generated doc. + Don't miss the <a href="globals.html">File member index</a>. + + */
\ No newline at end of file diff --git a/native_client_sdk/src/documentation/modules.dox b/native_client_sdk/src/documentation/modules.dox new file mode 100644 index 0000000..3923ccb --- /dev/null +++ b/native_client_sdk/src/documentation/modules.dox @@ -0,0 +1,22 @@ +// This is a doxygen file that describes the documentation groups + +/** + +@defgroup c_salt C Salt C++ API +Description of c_salt C++ API goes here. + +@defgroup examples c_salt examples +Here are the examples that use c_salt + +@defgroup npapi NPAPI bindings for c_salt +@ingroup c_salt +Defines the NPAPI bindings for c_salt + +@defgroup ppapi PPAPI bindings for c_salt +@ingroup c_salt +Defines the PPAPI (i.e. Pepper2) bindings for c_salt + +@defgroup tests c_salt integration and unit tests +Integration and unit tests for c_salt. + +*/ diff --git a/native_client_sdk/src/documentation/stylesheet-dox.css b/native_client_sdk/src/documentation/stylesheet-dox.css new file mode 100644 index 0000000..81d1a42 --- /dev/null +++ b/native_client_sdk/src/documentation/stylesheet-dox.css @@ -0,0 +1,478 @@ +body, table, div, p, dl { + font-family: Lucida Grande, Verdana, Geneva, Arial, sans-serif; + font-size: 12px; +} + +/* @group Heading Levels */ + +h1 { + text-align: center; + font-size: 150%; +} + +h1 a, h2 a, h3 a, h4 a { + font-weight:bold; +} + +h2 { + font-size: 120%; + margin-top: 2.0em; + margin-bottom: 0.5em; +} + +h3 { + font-size: 100%; +} + +div.contents { + margin-top: 2.0em; +} + +/* @end */ + +caption { + font-weight: bold; + font-size: 9px; +} + +div.qindex, div.navpath, div.navtab{ + background-color: #e8eef2; + border: 1px solid #84b0c7; + text-align: center; + margin: 2px; + padding: 2px; +} + +div.qindex, div.navpath { + width: 100%; + line-height: 140%; +} + +div.navtab { + margin-right: 15px; +} + +/* @group Link Styling */ + +a { + color: #153788; + font-weight: normal; + text-decoration: none; +} + +.contents a:visited { + color: #1b77c5; +} + +a:hover { + text-decoration: underline; +} + +a.qindex { + font-weight: bold; +} + +a.qindexHL { + font-weight: bold; + background-color: #6666cc; + color: #ffffff; + border: 1px double #9295C2; +} + +a.el { + font-weight: bold; +} + +a.elRef { +} + +a.code { +} + +a.codeRef { +} + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +.fragment { + font-family: monospace, fixed; + font-size: 105%; +} + +pre.fragment { + border: 1px solid #CCCCCC; + background-color: #f5f5f5; + padding: 4px 6px; + margin: 4px 8px 4px 2px; +} + +div.ah { + background-color: black; + font-weight: bold; + color: #ffffff; + margin-bottom: 3px; + margin-top: 3px +} + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + margin-bottom: 6px; + font-weight: bold; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +body { + background: white; + color: black; + margin-right: 20px; + margin-left: 20px; +} + +td.indexkey { + background-color: #e8eef2; + font-weight: bold; + border: 1px solid #CCCCCC; + margin: 2px 0px 2px 0; + padding: 2px 10px; +} + +td.indexvalue { + background-color: #e8eef2; + border: 1px solid #CCCCCC; + padding: 2px 10px; + margin: 2px 0px; +} + +tr.memlist { + background-color: #f0f0f0; +} + +p.formulaDsp { + text-align: center; +} + +img.formulaDsp { +} + +img.formulaInl { + vertical-align: middle; +} + +/* @group Code Colorization */ + +span.keyword { + color: #008000 +} + +span.keywordtype { + color: #604020 +} + +span.keywordflow { + color: #e08000 +} + +span.comment { + color: #800000 +} + +span.preprocessor { + color: #806020 +} + +span.stringliteral { + color: #002080 +} + +span.charliteral { + color: #008080 +} + +span.vhdldigit { + color: #ff00ff +} + +span.vhdlchar { + color: #000000 +} + +span.vhdlkeyword { + color: #700070 +} + +span.vhdllogic { + color: #ff0000 +} + +/* @end */ + +.search { + color: #003399; + font-weight: bold; +} + +form.search { + margin-bottom: 0px; + margin-top: 0px; +} + +input.search { + font-size: 75%; + color: #000080; + font-weight: normal; + background-color: #e8eef2; +} + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid #84b0c7; +} + +th.dirtab { + background: #e8eef2; + font-weight: bold; +} + +hr { + height: 0; + border: none; + border-top: 1px solid #666; +} + +/* @group Member Descriptions */ + +.mdescLeft, .mdescRight, +.memItemLeft, .memItemRight, +.memTemplItemLeft, .memTemplItemRight, .memTemplParams { + background-color: #FAFAFA; + border: none; + margin: 4px; + padding: 1px 0 0 8px; +} + +.mdescLeft, .mdescRight { + padding: 0px 8px 4px 8px; + color: #555; +} + +.memItemLeft, .memItemRight, .memTemplParams { + border-top: 1px solid #ccc; +} + +.memTemplParams { + color: #606060; +} + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtemplate { + font-size: 80%; + color: #606060; + font-weight: normal; + margin-left: 3px; +} + +.memnav { + background-color: #e8eef2; + border: 1px solid #84b0c7; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} + +.memitem { + padding: 0; +} + +.memname { + white-space: nowrap; + font-weight: bold; +} + +.memproto, .memdoc { + border: 1px solid #84b0c7; +} + +.memproto { + padding: 0; + background-color: #d5e1e8; + font-weight: bold; + -webkit-border-top-left-radius: 8px; + -webkit-border-top-right-radius: 8px; + -moz-border-radius-topleft: 8px; + -moz-border-radius-topright: 8px; +} + +.memdoc { + padding: 2px 5px; + background-color: #eef3f5; + border-top-width: 0; + -webkit-border-bottom-left-radius: 8px; + -webkit-border-bottom-right-radius: 8px; + -moz-border-radius-bottomleft: 8px; + -moz-border-radius-bottomright: 8px; +} + +.memdoc p, .memdoc dl, .memdoc ul { + margin: 6px 0; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; +} + +.paramname { + color: #602020; + white-space: nowrap; +} +.paramname em { + font-style: normal; +} + +/* @end */ + +/* @group Directory (tree) */ + +/* for the tree view */ + +.ftvtree { + font-family: sans-serif; + margin: 0.5em; +} + +/* these are for tree view when used as main index */ + +.directory { + font-size: 9pt; + font-weight: bold; +} + +.directory h3 { + margin: 0px; + margin-top: 1em; + font-size: 11pt; +} + +/* +The following two styles can be used to replace the root node title +with an image of your choice. Simply uncomment the next two styles, +specify the name of your image and be sure to set 'height' to the +proper pixel height of your image. +*/ + +/* +.directory h3.swap { + height: 61px; + background-repeat: no-repeat; + background-image: url("yourimage.gif"); +} +.directory h3.swap span { + display: none; +} +*/ + +.directory > h3 { + margin-top: 0; +} + +.directory p { + margin: 0px; + white-space: nowrap; +} + +.directory div { + display: none; + margin: 0px; +} + +.directory img { + vertical-align: -30%; +} + +/* these are for tree view when not used as main index */ + +.directory-alt { + font-size: 100%; + font-weight: bold; +} + +.directory-alt h3 { + margin: 0px; + margin-top: 1em; + font-size: 11pt; +} + +.directory-alt > h3 { + margin-top: 0; +} + +.directory-alt p { + margin: 0px; + white-space: nowrap; +} + +.directory-alt div { + display: none; + margin: 0px; +} + +.directory-alt img { + vertical-align: -30%; +} + +/* @end */ + +address { + font-style: normal; + text-align: center; + font-size: 90%; + color: gray; +} + +DIV.tabs A, #toplinks +{ + font-size : 9px; +} + +#toplinks { + text-align: right; + margin-bottom: -1.9em; +} + +.pending { + /* display:none; */ + color:red; font-weight:bold; +} + +#license { + color:gray; + font-size:90%; + border-top:1px solid; + border-color:gray; + padding-top:1em; + margin-top:3em; + text-align:center; +} diff --git a/native_client_sdk/src/documentation/stylesheet.css b/native_client_sdk/src/documentation/stylesheet.css new file mode 100644 index 0000000..3d17af0 --- /dev/null +++ b/native_client_sdk/src/documentation/stylesheet.css @@ -0,0 +1,101 @@ +@charset "utf-8"; +a:link { color: #0000cc; } +body { + background: #fff; margin: 3px 8px; + font-family: arial, sans-serif; +} + +body { font-size: 83%;} +pre, code, p kbd { font-size: 120%;} + +h1 { font-size: x-large; } +h2 { font-size: large; } +h3 { font-size: medium; } +h4 { font-size: small; } + +img { border: 1px solid #ccc; } + +pre { + margin-left: 2em; + padding: 0.5em; + border-left: 3px solid #ccc; +} + +pre.no-bar { + padding: 0; + border-left: 0; +} + +table { + border: 1px solid #999999; + border-collapse: collapse; +} + +th { + border: 1px solid #999999; + padding: 0.25em 0.5em; + background: #ccc; +} + +td { + border-bottom: 1px dotted #999999; + border-left: 1px dotted #999999; + text-align: left; + padding: 0.25em 0.5em; +} + +td pre.listing { + margin: 0.25em 0.25em 0.25em 0; +} + +pre.listing { + padding: 0; + background-color: #fff; + border: none; +} + +div#toplink { + font-size: small; + text-align: right; + margin-bottom: -2em; +} + +.caption { + font-weight:bold +} + +#license { + color:gray; + font-size:small; + border-top:1px solid; + border-color:gray; + padding:1em; + margin-top:3em; + text-align:center; +} + +.technote { + border:1px solid #999; + background:#ccc; + margin:0em 5em; + padding: 0.25em 0.5em; +} + +.notapplicable { + color:lightgray; + font-style:italic; +} + +pre kbd { + background:rgb(221, 248, 204); +} + +table caption { + font-style:italic; + text-align:left; +} + +.comment { + display:none; /* comment this line out if you want to see comments */ + color:red; font-weight:bold; +} diff --git a/native_client_sdk/src/examples/build.scons b/native_client_sdk/src/examples/build.scons new file mode 100644 index 0000000..f804d4b --- /dev/null +++ b/native_client_sdk/src/examples/build.scons @@ -0,0 +1,82 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import shutil +import sys + +""" +Build file for the NaCl SDK Examples + +This file runs all the scons files in the various example sub-directories. +Do not invoke this script directly, but instead use the scons or scons.bat +wrapper function. E.g. + +Linux or Mac: + ./scons [Options...] + +Windows: + scons.bat [Options...] +""" + +#------------------------------------------------------------------------------ +HELP_STRING = """ +=============================================================================== +Help for NaCl SDK Examples +=============================================================================== + +* cleaning: ./scons -c +* build a target: ./scons <target> +* clean a target: ./scons -c <target> + +Supported targets: + * fullscreen_tumbler Build the fullscreen-tumbler example. + * geturl Build the geturl example. + * hello_world Build the hello_world example. + * hello_world_c Build the hello_world_c example. + * input_events Build the input_events example. + * load_progress Build the load_progress example. + * mouselock Build the mouselock example. + * multithreaded_input_events Build the multithreaded input_events example. + * pi_generator Build the pi_generator example. + * pong Build the pong example. + * sine_synth Build the sine_synth example. + * tumbler Build the tumbler example. +""" + +example_directories = [ + 'fullscreen_tumbler', + 'geturl', + 'hello_world', + 'hello_world_c', + 'input_events', + 'load_progress', + 'mouselock', + 'multithreaded_input_events', + 'pi_generator', + 'pong', + 'sine_synth', + 'tumbler', + ] + +Help(HELP_STRING) + +staging_dir = os.path.abspath(os.getenv( + 'NACL_INSTALL_ROOT', os.path.join(os.getenv('NACL_SDK_ROOT', '.'), + 'staging'))) +general_files = Install(staging_dir, ['httpd.py']) +general_files.extend(InstallAs(os.path.join(staging_dir, 'index.html'), + 'index_staging.html')) + +if sys.platform in ['win32', 'cygwin']: + general_files.extend(Install(staging_dir, 'httpd.cmd')) + +SConscript([os.path.join(dir, 'build.scons') for dir in example_directories]) + +Default(['install'] + general_files + example_directories) +if GetOption('clean'): + print "Removing the staging directory at %s" % staging_dir + shutil.rmtree(staging_dir, ignore_errors=True) diff --git a/native_client_sdk/src/examples/common/check_browser.js b/native_client_sdk/src/examples/common/check_browser.js new file mode 100644 index 0000000..9636ad0 --- /dev/null +++ b/native_client_sdk/src/examples/common/check_browser.js @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2011 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** + * @fileoverview This file provides a BrowserChecker Javascript class. + * Users can create a BrowserChecker object, invoke checkBrowser(|version|), + * and then use getIsValidBrowser() and getBrowserSupportStatus() + * to determine if the browser version is greater than |version| + * and if the Native Client plugin is found. + */ + +// Create a namespace object +var browser_version = browser_version || {}; + +/** + * Class to provide checking for version and NativeClient. + * @param {integer} arg1 An argument that indicates major version of Chrome we + * require, such as 14. + */ + +/** + * Constructor for the BrowserChecker. Sets the major version of + * Chrome that is required to |minChromeVersion|. + * @param minChromeVersion The earliest major version of chrome that + * is supported. If the Chrome browser version is less than + * |minChromeVersion| then |isValidBrowswer| will be set to false. + * @param opt_maxChromeVersion Ignored. Retained for backwards compatibility. + * @param appVersion The application version string. + * @param plugins The plugins that exist in the browser. + * @constructor + */ +browser_version.BrowserChecker = function(minChromeVersion, + appVersion, plugins, + opt_maxChromeVersion) { + /** + * Version specified by the user. This class looks to see if the browser + * version is >= |minChromeVersion_|. + * @type {integer} + * @private + */ + this.minChromeVersion_ = minChromeVersion; + + /** + * List of Browser plugin objects. + * @type {Ojbect array} + * @private + */ + this.plugins_ = plugins; + + /** + * Application version string from the Browser. + * @type {integer} + * @private + */ + this.appVersion_ = appVersion; + + /** + * Flag used to indicate if the browser has Native Client and is if the + * browser version is recent enough. + * @type {boolean} + * @private + */ + this.isValidBrowser_ = false; + + /** + * Actual major version of Chrome -- found by querying the browser. + * @type {integer} + * @private + */ + this.chromeVersion_ = null; + + /** + * Browser support status. This allows the user to get a detailed status + * rather than using this.browserSupportMessage. + */ + this.browserSupportStatus_ = + browser_version.BrowserChecker.StatusValues.UNKNOWN; +} + +/** + * The values used for BrowserChecker status to indicate success or + * a specific error. + * @enum {id} + */ +browser_version.BrowserChecker.StatusValues = { + UNKNOWN: 0, + NACL_ENABLED: 1, + UNKNOWN_BROWSER: 2, + CHROME_VERSION_TOO_OLD: 3, + NACL_NOT_ENABLED: 4, + NOT_USING_SERVER: 5 +}; + +/** + * Determines if the plugin with name |name| exists in the browser. + * @param {string} name The name of the plugin. + * @param {Object array} plugins The plugins in this browser. + * @return {bool} |true| if the plugin is found. + */ +browser_version.BrowserChecker.prototype.pluginExists = function(name, + plugins) { + for (var index=0; index < plugins.length; index++) { + var plugin = this.plugins_[index]; + var plugin_name = plugin['name']; + // If the plugin is not found, you can use the Javascript console + // to see the names of the plugins that were found when debugging. + if (plugin_name.indexOf(name) != -1) { + return true; + } + } + return false; +} + +/** + * Returns browserSupportStatus_ which indicates if the browser supports + * Native Client. Values are defined as literals in + * browser_version.BrowserChecker.StatusValues. + * @ return {int} Level of NaCl support. + */ +browser_version.BrowserChecker.prototype.getBrowserSupportStatus = function() { + return this.browserSupportStatus_; +} + +/** + * Returns isValidBrowser (true/false) to indicate if the browser supports + * Native Client. + * @ return {bool} If this browser has NativeClient and correct version. + */ +browser_version.BrowserChecker.prototype.getIsValidBrowser = function() { + return this.isValidBrowser_; +} + +/** + * Checks to see if this browser can support Native Client applications. + * For Chrome browsers, checks to see if the "Native Client" plugin is + * enabled. + */ +browser_version.BrowserChecker.prototype.checkBrowser = function() { + var versionPatt = /Chrome\/(\d+)\.(\d+)\.(\d+)\.(\d+)/; + var result = this.appVersion_.match(versionPatt); + + // |result| stores the Chrome version number. + if (!result) { + this.isValidBrowser_ = false; + this.browserSupportStatus_ = + browser_version.BrowserChecker.StatusValues.UNKNOWN_BROWSER; + } else { + this.chromeVersion_ = result[1]; + // We know we have Chrome, check version and/or plugin named Native Client + if (this.chromeVersion_ >= this.minChromeVersion_) { + var found_nacl = this.pluginExists('Native Client', this.plugins_); + if (found_nacl) { + this.isValidBrowser_ = true; + this.browserSupportStatus_ = + browser_version.BrowserChecker.StatusValues.NACL_ENABLED; + } else { + this.isValidBrowser_ = false; + this.browserSupportStatus_ = + browser_version.BrowserChecker.StatusValues.NACL_NOT_ENABLED; + } + } else { + // We are in a version that is less than |minChromeVersion_| + this.isValidBrowser_ = false; + this.browserSupportStatus_ = + browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD; + } + } + var my_protocol = window.location.protocol; + if (my_protocol.indexOf('file') == 0) { + this.isValidBrowser_ = false; + this.browserSupportStatus_ = + browser_version.BrowserChecker.StatusValues.NOT_USING_SERVER; + } +} + diff --git a/native_client_sdk/src/examples/favicon.ico b/native_client_sdk/src/examples/favicon.ico Binary files differnew file mode 100644 index 0000000..ee7c943 --- /dev/null +++ b/native_client_sdk/src/examples/favicon.ico diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/bind.js b/native_client_sdk/src/examples/fullscreen_tumbler/bind.js new file mode 100644 index 0000000..92fbbd2 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/bind.js @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview This class implements an extension to Function object that + * lets you bind a scope for |this| to a function. + */ + +/** + * Bind a scope to a function. Used to bind an object to |this| for event + * handlers. + * @param {!Object} scope The scope in which the function executes. |scope| + * becomes |this| during function execution. + * @return {function} the bound version of the original function. + */ +Function.prototype.bind = function(scope) { + var boundContext = this; + return function() { + return boundContext.apply(scope, arguments); + } +} diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/build.scons b/native_client_sdk/src/examples/fullscreen_tumbler/build.scons new file mode 100644 index 0000000..26d14f2 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/build.scons @@ -0,0 +1,66 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import nacl_utils +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='fullscreen_tumbler', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + LIBS=['ppapi_gles2'], + ) + +sources = [ + 'cube.cc', + 'opengl_context.cc', + 'scripting_bridge.cc', + 'shader_util.cc', + 'transforms.cc', + 'tumbler.cc', + 'tumbler_module.cc', + ] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'fullscreen_tumbler') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('fullscreen_tumbler') + +common_files = [ + 'check_browser.js', + ] +common_files = [ + os.path.join(os.path.dirname(os.getcwd()), 'common', common_file) + for common_file in common_files] + +app_files = [ + 'fullscreen_tumbler.html', + 'fullscreen_tumbler.nmf', + 'bind.js', + 'dragger.js', + 'trackball.js', + 'tumbler.js', + 'vector3.js', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +common_dir = os.path.join(os.path.dirname(nacl_env['NACL_INSTALL_ROOT']), + 'common') +install_common = nacl_env.Install(dir=common_dir, source=common_files) +nacl_env.Alias('install', + source=[install_app, install_common, install_nexes]) diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/callback.h b/native_client_sdk/src/examples/fullscreen_tumbler/callback.h new file mode 100644 index 0000000..4d67262 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/callback.h @@ -0,0 +1,92 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_CALLBACK_H_ +#define EXAMPLES_TUMBLER_CALLBACK_H_ + +#include <map> +#include <string> +#include <vector> + +namespace tumbler { + +class ScriptingBridge; + +// Templates used to support method call-backs when a method or property is +// accessed from the browser code. + +// Class suite used to publish a method name to Javascript. Typical use is +// like this: +// photo::MethodCallback<Calculator>* calculate_callback_; +// calculate_callback_ = +// new scripting::MethodCallback<Calculator>(this, +// &Calculator::Calculate); +// bridge->AddMethodNamed("calculate", calculate_callback_); +// ... +// delete calculate_callback_; +// +// The caller must delete the callback. + +// Methods get parameters as a dictionary that maps parameter names to values. +typedef std::map<std::string, std::string> MethodParameter; + +// Pure virtual class used in STL containers. +class MethodCallbackExecutor { + public: + virtual ~MethodCallbackExecutor() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) = 0; +}; + +template <class T> +class MethodCallback : public MethodCallbackExecutor { + public: + typedef void (T::*Method)( + const ScriptingBridge& bridge, + const MethodParameter& parameters); + + MethodCallback(T* instance, Method method) + : instance_(instance), method_(method) {} + virtual ~MethodCallback() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) { + // Use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + ((this->instance_)->*(this->method_))(bridge, parameters); + } + + private: + T* instance_; + Method method_; +}; + +template <class T> +class ConstMethodCallback : public MethodCallbackExecutor { + public: + typedef void (T::*ConstMethod)( + const ScriptingBridge& bridge, + const MethodParameter& parameters) const; + + ConstMethodCallback(const T* instance, ConstMethod method) + : instance_(instance), const_method_(method) {} + virtual ~ConstMethodCallback() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) { + // Use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + ((this->instance_)->*(this->const_method_))(bridge, parameters); + } + + private: + const T* instance_; + ConstMethod const_method_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_CALLBACK_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/cube.cc b/native_client_sdk/src/examples/fullscreen_tumbler/cube.cc new file mode 100644 index 0000000..5b8bc4c --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/cube.cc @@ -0,0 +1,268 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/fullscreen_tumbler/cube.h" + +#include <algorithm> + +#include "examples/fullscreen_tumbler/shader_util.h" +#include "examples/fullscreen_tumbler/transforms.h" + +namespace tumbler { + +static const size_t kVertexCount = 24; +static const int kIndexCount = 36; + +Cube::Cube(SharedOpenGLContext opengl_context) + : opengl_context_(opengl_context), + width_(1), + height_(1) { + eye_[0] = eye_[1] = 0.0f; + eye_[2] = 2.0f; + orientation_[0] = 0.0f; + orientation_[1] = 0.0f; + orientation_[2] = 0.0f; + orientation_[3] = 1.0f; +} + +Cube::~Cube() { + glDeleteBuffers(3, cube_vbos_); + glDeleteProgram(shader_program_object_); +} + +void Cube::PrepareOpenGL() { + CreateShaders(); + CreateCube(); + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glEnable(GL_DEPTH_TEST); +} + +void Cube::Resize(int width, int height) { + width_ = std::max(width, 1); + height_ = std::max(height, 1); + // Set the viewport + glViewport(0, 0, width_, height_); + // Compute the perspective projection matrix with a 60 degree FOV. + GLfloat aspect = static_cast<GLfloat>(width_) / static_cast<GLfloat>(height_); + transform_4x4::LoadIdentity(perspective_proj_); + transform_4x4::Perspective(perspective_proj_, 60.0f, aspect, 1.0f, 20.0f); +} + +void Cube::Draw() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Compute a new model-view matrix, then use that to make the composite + // model-view-projection matrix: MVP = MV . P. + GLfloat model_view[16]; + ComputeModelViewTransform(model_view); + transform_4x4::Multiply(mvp_matrix_, model_view, perspective_proj_); + + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]); + glUseProgram(shader_program_object_); + glEnableVertexAttribArray(position_location_); + glVertexAttribPointer(position_location_, + 3, + GL_FLOAT, + GL_FALSE, + 3 * sizeof(GLfloat), + NULL); + glEnableVertexAttribArray(color_location_); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]); + glVertexAttribPointer(color_location_, + 3, + GL_FLOAT, + GL_FALSE, + 3 * sizeof(GLfloat), + NULL); + glUniformMatrix4fv(mvp_location_, 1, GL_FALSE, mvp_matrix_); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]); + glDrawElements(GL_TRIANGLES, kIndexCount, GL_UNSIGNED_SHORT, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +bool Cube::CreateShaders() { + const char vertex_shader_src[] = + "uniform mat4 u_mvpMatrix; \n" + "attribute vec4 a_position; \n" + "attribute vec3 a_color; \n" + "varying lowp vec4 v_color; \n" + "void main() \n" + "{ \n" + " v_color.xyz = a_color; \n" + " v_color.w = 1.0; \n" + " gl_Position = u_mvpMatrix * a_position; \n" + "} \n"; + + const char fragment_shader_src[] = + "varying lowp vec4 v_color; \n" + "void main() \n" + "{ \n" + " gl_FragColor = v_color; \n" + "} \n"; + + // Load the shaders and get a linked program object + shader_program_object_ = + shader_util::CreateProgramFromVertexAndFragmentShaders( + vertex_shader_src, fragment_shader_src); + if (shader_program_object_ == 0) + return false; + position_location_ = glGetAttribLocation(shader_program_object_, + "a_position"); + color_location_ = glGetAttribLocation(shader_program_object_, "a_color"); + mvp_location_ = glGetUniformLocation(shader_program_object_, "u_mvpMatrix"); + return true; +} + +void Cube::CreateCube() { + static const GLfloat cube_vertices[] = { + // Vertex coordinates interleaved with color values + // Bottom + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, -0.5f, + // Top + -0.5f, 0.5f, -0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f, + // Back + -0.5f, -0.5f, -0.5f, + -0.5f, 0.5f, -0.5f, + 0.5f, 0.5f, -0.5f, + 0.5f, -0.5f, -0.5f, + // Front + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + // Left + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + -0.5f, 0.5f, -0.5f, + // Right + 0.5f, -0.5f, -0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f + }; + + static const GLfloat cube_colors[] = { + // Vertex coordinates interleaved with color values + // Bottom + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + // Top + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + // Back + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + // Front + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + // Left + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + // Right + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0 + }; + + static const GLushort cube_indices[] = { + // Bottom + 0, 2, 1, + 0, 3, 2, + // Top + 4, 5, 6, + 4, 6, 7, + // Back + 8, 9, 10, + 8, 10, 11, + // Front + 12, 15, 14, + 12, 14, 13, + // Left + 16, 17, 18, + 16, 18, 19, + // Right + 20, 23, 22, + 20, 22, 21 + }; + + // Generate the VBOs and upload them to the graphics context. + glGenBuffers(3, cube_vbos_); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]); + glBufferData(GL_ARRAY_BUFFER, + kVertexCount * sizeof(GLfloat) * 3, + cube_vertices, + GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]); + glBufferData(GL_ARRAY_BUFFER, + kVertexCount * sizeof(GLfloat) * 3, + cube_colors, + GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + kIndexCount * sizeof(GL_UNSIGNED_SHORT), + cube_indices, + GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +void Cube::ComputeModelViewTransform(GLfloat* model_view) { + // This method takes into account the possiblity that |orientation_| + // might not be normalized. + double sqrx = orientation_[0] * orientation_[0]; + double sqry = orientation_[1] * orientation_[1]; + double sqrz = orientation_[2] * orientation_[2]; + double sqrw = orientation_[3] * orientation_[3]; + double sqrLength = 1.0 / (sqrx + sqry + sqrz + sqrw); + + transform_4x4::LoadIdentity(model_view); + model_view[0] = (sqrx - sqry - sqrz + sqrw) * sqrLength; + model_view[5] = (-sqrx + sqry - sqrz + sqrw) * sqrLength; + model_view[10] = (-sqrx - sqry + sqrz + sqrw) * sqrLength; + + double temp1 = orientation_[0] * orientation_[1]; + double temp2 = orientation_[2] * orientation_[3]; + model_view[1] = 2.0 * (temp1 + temp2) * sqrLength; + model_view[4] = 2.0 * (temp1 - temp2) * sqrLength; + + temp1 = orientation_[0] * orientation_[2]; + temp2 = orientation_[1] * orientation_[3]; + model_view[2] = 2.0 * (temp1 - temp2) * sqrLength; + model_view[8] = 2.0 * (temp1 + temp2) * sqrLength; + temp1 = orientation_[1] * orientation_[2]; + temp2 = orientation_[0] * orientation_[3]; + model_view[6] = 2.0 * (temp1 + temp2) * sqrLength; + model_view[9] = 2.0 * (temp1 - temp2) * sqrLength; + model_view[3] = 0.0; + model_view[7] = 0.0; + model_view[11] = 0.0; + + // Concatenate the translation to the eye point. + model_view[12] = -eye_[0]; + model_view[13] = -eye_[1]; + model_view[14] = -eye_[2]; + model_view[15] = 1.0; +} + +} // namespace tumbler + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/cube.h b/native_client_sdk/src/examples/fullscreen_tumbler/cube.h new file mode 100644 index 0000000..42af1cc --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/cube.h @@ -0,0 +1,98 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_CUBE_H_ +#define EXAMPLES_TUMBLER_CUBE_H_ + +#include <GLES2/gl2.h> +#include <vector> +#include "examples/fullscreen_tumbler/opengl_context.h" +#include "examples/fullscreen_tumbler/opengl_context_ptrs.h" + +namespace tumbler { + +// The Cube class provides a place to implement 3D rendering. It has a +// frame that it occupies in a browser window. +class Cube { + public: + explicit Cube(SharedOpenGLContext opengl_context); + ~Cube(); + + // Called once when a new RenderContext is first bound to the view. The + // bound context is guaranteed to be current and valid before calling this + // method. + void PrepareOpenGL(); + + // Called whenever the size of the browser view changes. This method is + // called at least once when the view is first made visible. Clamps the + // sizes to 1. + void Resize(int width, int height); + + // Called every time the view need to be drawn. The bound context is + // guaranteed to be current and valid before this method is called. The + // visible portion of the context is flushed to the browser after this + // method returns. + void Draw(); + + // Accessor for width and height. To change these, call Resize. + const int width() const { + return width_; + } + + const int height() const { + return height_; + } + + // Accessor/mutator for the camera orientation. + void GetOrientation(std::vector<float>* orientation) const { + if (!orientation) + return; + (*orientation)[0] = static_cast<float>(orientation_[0]); + (*orientation)[1] = static_cast<float>(orientation_[1]); + (*orientation)[2] = static_cast<float>(orientation_[2]); + (*orientation)[3] = static_cast<float>(orientation_[3]); + } + void SetOrientation(const std::vector<float>& orientation) { + orientation_[0] = static_cast<GLfloat>(orientation[0]); + orientation_[1] = static_cast<GLfloat>(orientation[1]); + orientation_[2] = static_cast<GLfloat>(orientation[2]); + orientation_[3] = static_cast<GLfloat>(orientation[3]); + } + + private: + // Create the shaders used to draw the cube, and link them into a program. + // Initializes |shader_progam_object_|, |position_loction_| and + // |mvp_location_|. + bool CreateShaders(); + + // Generates a cube as a series of GL_TRIANGLE_STRIPs, and initializes + // |index_count_| to the number of indices in the index list used as a VBO. + // Creates the |vbo_ids_| required for the vertex and index data and uploads + // the the VBO data. + void CreateCube(); + + // Build up the model-view transform from the eye and orienation properties. + // Assumes that |model_view| is a 4x4 matrix. + void ComputeModelViewTransform(GLfloat* model_view); + + SharedOpenGLContext opengl_context_; + int width_; + int height_; + GLuint shader_program_object_; // The compiled shaders. + GLint position_location_; // The position attribute location. + GLint color_location_; // The color attribute location. + GLint mvp_location_; // The Model-View-Projection composite matrix. + GLuint cube_vbos_[3]; + GLfloat eye_[3]; // The eye point of the virtual camera. + // The orientation of the virtual camera stored as a quaternion. The + // quaternion is laid out as {{x, y, z}, w}. + GLfloat orientation_[4]; + GLfloat perspective_proj_[16]; + GLfloat mvp_matrix_[16]; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_CUBE_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/dragger.js b/native_client_sdk/src/examples/fullscreen_tumbler/dragger.js new file mode 100644 index 0000000..232d8b5 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/dragger.js @@ -0,0 +1,134 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview This class implements a mouse-drag event. It registers for + * mousedown events, and when it sees one, starts capturing mousemove events + * until it gets a mousup event. It manufactures three drag events: the + * DRAG_START, DRAG and DRAG_END. + */ + +// Requires bind + +/** + * Constructor for the Dragger. Register for mousedown events that happen on + * |opt_target|. If |opt_target| is null or undefined, then this object + * observes mousedown on the whole document. + * @param {?Element} opt_target The event target. Defaults to the whole + * document. + * @constructor + */ +tumbler.Dragger = function(opt_target) { + /** + * The event target. + * @type {Element} + * @private + */ + this.target_ = opt_target || document; + + /** + * The array of objects that get notified of drag events. Each object in + * this array get sent a handleStartDrag(), handleDrag() and handleEndDrag() + * message. + * @type {Array.<Object>} + * @private + */ + this.listeners_ = []; + + /** + * Flag to indicate whether the object is in a drag sequence or not. + * @type {boolean} + * @private + */ + this.isDragging_ = false; + + /** + * The function objects that get attached as event handlers. These are + * cached so that they can be removed on mouse up. + * @type {function} + * @private + */ + this.boundMouseMove_ = null; + this.boundMouseUp_ = null; + + this.target_.addEventListener('mousedown', + this.onMouseDown.bind(this), + false); +} + +/** + * The ids used for drag event types. + * @enum {string} + */ +tumbler.Dragger.DragEvents = { + DRAG_START: 'dragstart', // Start a drag sequence + DRAG: 'drag', // Mouse moved during a drag sequence. + DRAG_END: 'dragend' // End a drag sewquence. +}; + +/** + * Add a drag listener. Each listener should respond to thhree methods: + * handleStartDrag(), handleDrag() and handleEndDrag(). This method assumes + * that |listener| does not already exist in the array of listeners. + * @param {!Object} listener The object that will listen to drag events. + */ +tumbler.Dragger.prototype.addDragListener = function(listener) { + this.listeners_.push(listener); +} + +/** + * Handle a mousedown event: register for mousemove and mouseup, then tell + * the target that is has a DRAG_START event. + * @param {Event} event The mousedown event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseDown = function(event) { + this.boundMouseMove_ = this.onMouseMove.bind(this); + this.boundMouseUp_ = this.onMouseUp.bind(this); + this.target_.addEventListener('mousemove', this.boundMouseMove_); + this.target_.addEventListener('mouseup', this.boundMouseUp_); + this.isDragging_ = true; + var dragStartEvent = { type: tumbler.Dragger.DragEvents.DRAG_START, + clientX: event.offsetX, + clientY: event.offsetY }; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleStartDrag(this.target_, dragStartEvent); + } +} + +/** + * Handle a mousemove event: tell the target that is has a DRAG event. + * @param {Event} event The mousemove event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseMove = function(event) { + if (!this.isDragging_) + return; + var dragEvent = { type: tumbler.Dragger.DragEvents.DRAG, + clientX: event.offsetX, + clientY: event.offsetY}; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleDrag(this.target_, dragEvent); + } +} + +/** + * Handle a mouseup event: un-register for mousemove and mouseup, then tell + * the target that is has a DRAG_END event. + * @param {Event} event The mouseup event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseUp = function(event) { + this.target_.removeEventListener('mouseup', this.boundMouseUp_, false); + this.target_.removeEventListener('mousemove', this.boundMouseMove_, false); + this.boundMouseUp_ = null; + this.boundMouseMove_ = null; + this.isDragging_ = false; + var dragEndEvent = { type: tumbler.Dragger.DragEvents.DRAG_END, + clientX: event.offsetX, + clientY: event.offsetY}; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleEndDrag(this.target_, dragEndEvent); + } +} diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/fullscreen_tumbler.html b/native_client_sdk/src/examples/fullscreen_tumbler/fullscreen_tumbler.html new file mode 100644 index 0000000..a255b66 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/fullscreen_tumbler.html @@ -0,0 +1,57 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + <head> + <title>Interactive Cube Fullscreen Example</title> + <script type="text/javascript"> + // Provide the tumbler namespace + tumbler = {}; + // Fullscreen support is in Chrome version 16. + tumbler.CHROME_MINIMUM_VERSION = 16; + </script> + <script type="text/javascript" src="../common/check_browser.js"></script> + <script type="text/javascript" src="bind.js"></script> + <script type="text/javascript" src="dragger.js"></script> + <script type="text/javascript" src="tumbler.js"></script> + <script type="text/javascript" src="vector3.js"></script> + <script type="text/javascript" src="trackball.js"></script> + <script type="text/javascript"> + // Check for Native Client support in the browser before the DOM loads. + var isValidBrowser = false; + var browserSupportStatus = 0; + var checker = new browser_version.BrowserChecker( + tumbler.CHROME_MINIMUM_VERSION, + navigator["appVersion"], + navigator["plugins"]); + checker.checkBrowser(); + + isValidBrowser = checker.getIsValidBrowser(); + browserSupportStatus = checker.getBrowserSupportStatus(); + </script> + </head> + <body id="bodyId"> + <h1>Interactive Cube Example</h1> + <p> + The Native Client module executed in this page draws a 3D cube + and allows you to rotate it using a virtual trackball method. To toggle + the view to/from fullscreen, press the Enter key. + </p> + <div id="tumbler_view"></div> + <script type="text/javascript"> + if (browserSupportStatus == + browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD) { + alert('This example will only work on Chrome version ' + + tumbler.CHROME_MINIMUM_VERSION + + ' or later.'); + } else { + tumbler.application = new tumbler.Application(); + tumbler.application.run('tumbler_view'); + } + </script> + </body> +</HTML> diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.cc b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.cc new file mode 100644 index 0000000..98ee3c3 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/fullscreen_tumbler/opengl_context.h" + +#include <pthread.h> +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/gles2/gl2ext_ppapi.h" + +namespace { +// This is called by the brower when the 3D context has been flushed to the +// browser window. +void FlushCallback(void* data, int32_t result) { + static_cast<tumbler::OpenGLContext*>(data)->set_flush_pending(false); +} +} // namespace + +namespace tumbler { + +OpenGLContext::OpenGLContext(pp::Instance* instance) + : pp::Graphics3DClient(instance), + flush_pending_(false) { + pp::Module* module = pp::Module::Get(); + assert(module); + gles2_interface_ = static_cast<const struct PPB_OpenGLES2*>( + module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE)); + assert(gles2_interface_); +} + +OpenGLContext::~OpenGLContext() { + glSetCurrentContextPPAPI(0); +} + +bool OpenGLContext::MakeContextCurrent(pp::Instance* instance) { + if (instance == NULL) { + glSetCurrentContextPPAPI(0); + return false; + } + // Lazily create the Pepper context. + if (context_.is_null()) { + int32_t attribs[] = { + PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8, + PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24, + PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 8, + PP_GRAPHICS3DATTRIB_SAMPLES, 0, + PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0, + PP_GRAPHICS3DATTRIB_WIDTH, size_.width(), + PP_GRAPHICS3DATTRIB_HEIGHT, size_.height(), + PP_GRAPHICS3DATTRIB_NONE + }; + context_ = pp::Graphics3D(instance, pp::Graphics3D(), attribs); + if (context_.is_null()) { + glSetCurrentContextPPAPI(0); + return false; + } + instance->BindGraphics(context_); + } + glSetCurrentContextPPAPI(context_.pp_resource()); + return true; +} + +void OpenGLContext::InvalidateContext(pp::Instance* instance) { + glSetCurrentContextPPAPI(0); +} + +void OpenGLContext::ResizeContext(const pp::Size& size) { + size_ = size; + if (!context_.is_null()) { + context_.ResizeBuffers(size.width(), size.height()); + } +} + + +void OpenGLContext::FlushContext() { + if (flush_pending()) { + // A flush is pending so do nothing; just drop this flush on the floor. + return; + } + set_flush_pending(true); + context_.SwapBuffers(pp::CompletionCallback(&FlushCallback, this)); +} +} // namespace tumbler + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.h b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.h new file mode 100644 index 0000000..6a5369b --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.h @@ -0,0 +1,94 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ +#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ + +/// +/// @file +/// OpenGLContext manages the OpenGL context in the browser that is associated +/// with a @a pp::Instance instance. +/// + +#include <assert.h> +#include <pthread.h> + +#include <algorithm> +#include <string> + +#include "examples/fullscreen_tumbler/opengl_context_ptrs.h" +#include "ppapi/c/ppb_opengles2.h" +#include "ppapi/cpp/graphics_3d.h" +#include "ppapi/cpp/graphics_3d_client.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/size.h" + +namespace tumbler { + +/// OpenGLContext manages an OpenGL rendering context in the browser. +/// +class OpenGLContext : public pp::Graphics3DClient { + public: + explicit OpenGLContext(pp::Instance* instance); + + /// Release all the in-browser resources used by this context, and make this + /// context invalid. + virtual ~OpenGLContext(); + + /// The Graphics3DClient interfcace. + virtual void Graphics3DContextLost() { + assert(!"Unexpectedly lost graphics context"); + } + + /// Make @a this the current 3D context in @a instance. + /// @param instance The instance of the NaCl module that will receive the + /// the current 3D context. + /// @return success. + bool MakeContextCurrent(pp::Instance* instance); + + /// Flush the contents of this context to the browser's 3D device. + void FlushContext(); + + /// Make the underlying 3D device invalid, so that any subsequent rendering + /// commands will have no effect. The next call to MakeContextCurrent() will + /// cause the underlying 3D device to get rebound and start receiving + /// receiving rendering commands again. Use InvalidateContext(), for + /// example, when resizing the context's viewing area. + void InvalidateContext(pp::Instance* instance); + + /// Resize the context. + void ResizeContext(const pp::Size& size); + + /// The OpenGL ES 2.0 interface. + const struct PPB_OpenGLES2* gles2() const { + return gles2_interface_; + } + + /// The PP_Resource needed to make GLES2 calls through the Pepper interface. + const PP_Resource gl_context() const { + return context_.pp_resource(); + } + + /// Indicate whether a flush is pending. This can only be called from the + /// main thread; it is not thread safe. + bool flush_pending() const { + return flush_pending_; + } + void set_flush_pending(bool flag) { + flush_pending_ = flag; + } + + private: + pp::Size size_; + pp::Graphics3D context_; + bool flush_pending_; + + const struct PPB_OpenGLES2* gles2_interface_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ + + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context_ptrs.h b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context_ptrs.h new file mode 100644 index 0000000..3478521 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context_ptrs.h @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ +#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ + +// A convenience wrapper for a shared OpenGLContext pointer type. As other +// smart pointer types are needed, add them here. + +#include <tr1/memory> + +namespace tumbler { + +class OpenGLContext; + +typedef std::tr1::shared_ptr<OpenGLContext> SharedOpenGLContext; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.cc b/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.cc new file mode 100644 index 0000000..8a7a54d --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/fullscreen_tumbler/scripting_bridge.h" + +namespace { +const char* const kWhiteSpaceCharacters = " \t"; + +// Helper function to pull out the next token in |token_string|. A token is +// delimited by whitespace. Scanning begins at |*pos|, if pos goes beyond the +// end of |token_string|, it is set to std::string::npos and an empty string +// is returned. On return, |*pos| will point to the beginning of the next +// token. |pos| must not be NULL. +const std::string ScanToken(const std::string& token_string, size_t* pos) { + std::string token; + if (*pos == std::string::npos) { + return token; + } + size_t token_start_pos = token_string.find_first_not_of(kWhiteSpaceCharacters, + *pos); + size_t token_end_pos = token_string.find_first_of(kWhiteSpaceCharacters, + token_start_pos); + if (token_start_pos != std::string::npos) { + token = token_string.substr(token_start_pos, token_end_pos); + } + *pos = token_end_pos; + return token; +} + +// Take a string of the form 'name:value' and split it into two strings, one +// containing 'name' and the other 'value'. If the ':' separator is missing, +// or is the last character in |parameter|, |parameter| is copied to +// |param_name|, |param_value| is left unchanged and false is returned. +bool ParseParameter(const std::string& parameter, + std::string* param_name, + std::string* param_value) { + bool success = false; + size_t sep_pos = parameter.find_first_of(':'); + if (sep_pos != std::string::npos) { + *param_name = parameter.substr(0, sep_pos); + if (sep_pos < parameter.length() - 1) { + *param_value = parameter.substr(sep_pos + 1); + success = true; + } else { + success = false; + } + } else { + *param_name = parameter; + success = false; + } + return success; +} +} // namespace + +namespace tumbler { + +bool ScriptingBridge::AddMethodNamed(const std::string& method_name, + SharedMethodCallbackExecutor method) { + if (method_name.size() == 0 || method == NULL) + return false; + method_dictionary_.insert( + std::pair<std::string, SharedMethodCallbackExecutor>(method_name, + method)); + return true; +} + +bool ScriptingBridge::InvokeMethod(const std::string& method) { + size_t current_pos = 0; + const std::string method_name = ScanToken(method, ¤t_pos); + MethodDictionary::iterator method_iter; + method_iter = method_dictionary_.find(method_name); + if (method_iter != method_dictionary_.end()) { + // Pull out the method parameters and build a dictionary that maps + // parameter names to values. + std::map<std::string, std::string> param_dict; + while (current_pos != std::string::npos) { + const std::string parameter = ScanToken(method, ¤t_pos); + if (parameter.length()) { + std::string param_name; + std::string param_value; + if (ParseParameter(parameter, ¶m_name, ¶m_value)) { + // Note that duplicate parameter names will override each other. The + // last one in the method string will be used. + param_dict[param_name] = param_value; + } + } + } + (*method_iter->second).Execute(*this, param_dict); + return true; + } + return false; +} + +} // namespace tumbler + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.h b/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.h new file mode 100644 index 0000000..b1888cf --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.h @@ -0,0 +1,53 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ +#define EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ + +#include <map> +#include <string> +#include <tr1/memory> +#include <vector> + +#include "examples/fullscreen_tumbler/callback.h" +#include "ppapi/cpp/var.h" + +namespace tumbler { + +class MethodCallbackExecutor; + +// This class handles the interface between the browser and the NaCl module. +// There is a single point of entry from the browser: postMessage(). The +// string passed to postMessage() has this format: +// 'function_name arg_name0:arg_0 arg_name1:arg1 ...' +// The arguments have undetermined type; they are placed in a map of argument +// names and values. Values are all strings, it is up to the target code to +// do any type coercion. +// Methods called by the scripting bridge must have a signature like this: +// void Method(const ScriptingBridge& bridge, +// const ParameterDictionary&); +class ScriptingBridge { + public: + // Shared pointer type used in the method map. + typedef std::tr1::shared_ptr<MethodCallbackExecutor> + SharedMethodCallbackExecutor; + + virtual ~ScriptingBridge() {} + + // Causes |method_name| to be published as a method that can be called via + // postMessage() from the browser. Associates this method with |method|. + bool AddMethodNamed(const std::string& method_name, + SharedMethodCallbackExecutor method); + + bool InvokeMethod(const std::string& method); + + private: + typedef std::map<std::string, SharedMethodCallbackExecutor> MethodDictionary; + + MethodDictionary method_dictionary_; +}; + +} // namespace tumbler +#endif // EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.cc b/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.cc new file mode 100644 index 0000000..c2c647d --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/fullscreen_tumbler/shader_util.h" + +#include <stdio.h> +#include <stdlib.h> + +namespace shader_util { + +GLuint CreateShaderOfType(GLenum type, const char *shader_src) { + GLuint shader; + GLint compiled; + + // Create the shader object + shader = glCreateShader(type); + + if (shader == 0) + return 0; + + // Load and compile the shader source + glShaderSource(shader, 1, &shader_src, NULL); + glCompileShader(shader); + + // Check the compile status + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (compiled == 0) { + GLint info_len = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len); + if (info_len > 1) { + char* info_log = + reinterpret_cast<char*>(malloc(sizeof(*info_log) * info_len)); + glGetShaderInfoLog(shader, info_len, NULL, info_log); + // TODO(dspringer): We could really use a logging API. + printf("Error compiling shader:\n%s\n", info_log); + free(info_log); + } + glDeleteShader(shader); + return 0; + } + + return shader; +} + +GLuint CreateProgramFromVertexAndFragmentShaders( + const char *vertex_shader_src, const char *fragment_shader_src) { + GLuint vertex_shader; + GLuint fragment_shader; + GLuint program_object; + GLint linked; + + // Load the vertex/fragment shaders + vertex_shader = CreateShaderOfType(GL_VERTEX_SHADER, vertex_shader_src); + if (vertex_shader == 0) + return 0; + fragment_shader = CreateShaderOfType(GL_FRAGMENT_SHADER, fragment_shader_src); + if (fragment_shader == 0) { + glDeleteShader(vertex_shader); + return 0; + } + + // Create the program object and attach the shaders. + program_object = glCreateProgram(); + if (program_object == 0) + return 0; + glAttachShader(program_object, vertex_shader); + glAttachShader(program_object, fragment_shader); + + // Link the program + glLinkProgram(program_object); + + // Check the link status + glGetProgramiv(program_object, GL_LINK_STATUS, &linked); + if (linked == 0) { + GLint info_len = 0; + glGetProgramiv(program_object, GL_INFO_LOG_LENGTH, &info_len); + if (info_len > 1) { + char* info_log = reinterpret_cast<char*>(malloc(info_len)); + glGetProgramInfoLog(program_object, info_len, NULL, info_log); + // TODO(dspringer): We could really use a logging API. + printf("Error linking program:\n%s\n", info_log); + free(info_log); + } + glDeleteProgram(program_object); + return 0; + } + + // Delete these here because they are attached to the program object. + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return program_object; +} + +} // namespace shader_util + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.h b/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.h new file mode 100644 index 0000000..fdf9cbd --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.h @@ -0,0 +1,30 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Some simple helper functions that load shaders and create program objects. + +#ifndef EXAMPLES_TUMBLER_SHADER_UTIL_H_ +#define EXAMPLES_TUMBLER_SHADER_UTIL_H_ + +#include <GLES2/gl2.h> + +namespace shader_util { + +// Load and compile a shader. |type| can be one of GL_VERTEX_SHADER or +// GL_FRAGMENT_SHADER. Returns a non-0 value representing the compiled +// shader on success, 0 on failure. The caller is responsible for deleting +// the returned shader using glDeleteShader(). +GLuint CreateShaderOfType(GLenum type, const char *shader_src); + +// Load and compile the vertex and fragment shaders, then link these together +// into a complete program. Returns a non-0 value representing the program on, +// success or 0 on failure. The caller is responsible for deleting the +// returned program using glDeleteProgram(). +GLuint CreateProgramFromVertexAndFragmentShaders( + const char *vertex_shader_src, const char *fragment_shader_src); + +} // namespace shader_util + +#endif // EXAMPLES_TUMBLER_SHADER_UTIL_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/trackball.js b/native_client_sdk/src/examples/fullscreen_tumbler/trackball.js new file mode 100644 index 0000000..88b9a62 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/trackball.js @@ -0,0 +1,296 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Implement a virtual trackball in the tumbler.Trackball + * class. This class maps 2D mouse events to 3D rotations by simulating a + * trackball that you roll by dragging the mouse. There are two principle + * methods in the class: startAtPointInFrame which you use to begin a trackball + * simulation and rollToPoint, which you use while dragging the mouse. The + * rollToPoint method returns a rotation expressed as a quaternion. + */ + + +// Requires tumbler.Application +// Requires tumbler.DragEvent +// Requires tumbler.Vector3 + +/** + * Constructor for the Trackball object. This class maps 2D mouse drag events + * into 3D rotations by simulating a trackball. The idea is to simulate + * clicking on the trackball, and then rolling it as you drag the mouse. + * The math behind the trackball is simple: start with a vector from the first + * mouse-click on the ball to the center of the 3D view. At the same time, set + * the radius of the ball to be the smaller dimension of the 3D view. As you + * drag the mouse around in the 3D view, a second vector is computed from the + * surface of the ball to the center. The axis of rotation is the cross + * product of these two vectors, and the angle of rotation is the angle between + * the two vectors. + * @constructor + */ +tumbler.Trackball = function() { + /** + * The square of the trackball's radius. The math never looks at the radius, + * but looks at the radius squared. + * @type {number} + * @private + */ + this.sqrRadius_ = 0; + + /** + * The 3D vector representing the point on the trackball where the mouse + * was clicked. Default is pointing stright through the center of the ball. + * @type {Object} + * @private + */ + this.rollStart_ = new tumbler.Vector3(0, 0, 1); + + /** + * The 2D center of the frame that encloses the trackball. + * @type {!Object} + * @private + */ + this.center_ = { x: 0, y: 0 }; + + /** + * Cached camera orientation. When a drag START event happens this is set to + * the current orientation in the calling view's plugin. The default is the + * identity quaternion. + * @type {Array.<number>} + * @private + */ + this.cameraOrientation_ = [0, 0, 0, 1]; +}; + +/** + * Compute the dimensions of the virtual trackball to fit inside |frameSize|. + * The radius of the trackball is set to be 1/2 of the smaller of the two frame + * dimensions, the center point is at the midpoint of each side. + * @param {!goog.math.Size} frameSize 2D-point representing the size of the + * element that encloses the virtual trackball. + * @private + */ +tumbler.Trackball.prototype.initInFrame_ = function(frameSize) { + // Compute the radius of the virtual trackball. This is 1/2 of the smaller + // of the frame's width and height. + var halfFrameSize = 0.5 * Math.min(frameSize.width, frameSize.height); + // Cache the square of the trackball's radius. + this.sqrRadius_ = halfFrameSize * halfFrameSize; + // Figure the center of the view. + this.center_.x = frameSize.width * 0.5; + this.center_.y = frameSize.height * 0.5; +}; + +/** + * Method to convert (by translation) a 2D client point from a coordinate space + * with origin in the lower-left corner of the client view to a space with + * origin in the center of the client view. Use this method before mapping the + * 2D point to he 3D tackball point (see also the projectOnTrackball_() method). + * Call the startAtPointInFrame before calling this method so that the + * |center_| property is correctly initialized. + * @param {!Object} clientPoint map this point to the coordinate space with + * origin in thecenter of the client view. + * @return {Object} the converted point. + * @private + */ +tumbler.Trackball.prototype.convertClientPoint_ = function(clientPoint) { + var difference = { x: clientPoint.x - this.center_.x, + y: clientPoint.y - this.center_.y } + return difference; +}; + +/** + * Method to map a 2D point to a 3D point on the virtual trackball that was set + * up using the startAtPointInFrame method. If the point lies outside of the + * radius of the virtual trackball, then the z-coordinate of the 3D point + * is set to 0. + * @param {!Object.<x, y>} point 2D-point in the coordinate space with origin + * in the center of the client view. + * @return {tumbler.Vector3} the 3D point on the virtual trackball. + * @private + */ +tumbler.Trackball.prototype.projectOnTrackball_ = function(point) { + var sqrRadius2D = point.x * point.x + point.y * point.y; + var zValue; + if (sqrRadius2D > this.sqrRadius_) { + // |point| lies outside the virtual trackball's sphere, so use a virtual + // z-value of 0. This is equivalent to clicking on the horizontal equator + // of the trackball. + zValue = 0; + } else { + // A sphere can be defined as: r^2 = x^2 + y^2 + z^2, so z = + // sqrt(r^2 - (x^2 + y^2)). + zValue = Math.sqrt(this.sqrRadius_ - sqrRadius2D); + } + var trackballPoint = new tumbler.Vector3(point.x, point.y, zValue); + return trackballPoint; +}; + +/** + * Method to start up the trackball. The trackball works by pretending that a + * ball encloses the 3D view. You roll this pretend ball with the mouse. For + * example, if you click on the center of the ball and move the mouse straight + * to the right, you roll the ball around its Y-axis. This produces a Y-axis + * rotation. You can click on the "edge" of the ball and roll it around + * in a circle to get a Z-axis rotation. + * @param {!Object.<x, y>} startPoint 2D-point, usually the mouse-down + * point. + * @param {!Object.<width, height>} frameSize 2D-point representing the size of + * the element that encloses the virtual trackball. + */ +tumbler.Trackball.prototype.startAtPointInFrame = + function(startPoint, frameSize) { + this.initInFrame_(frameSize); + // Compute the starting vector from the surface of the ball to its center. + this.rollStart_ = this.projectOnTrackball_( + this.convertClientPoint_(startPoint)); +}; + +/** + * Method to roll the virtual trackball; call this in response to a mouseDrag + * event. Takes |dragPoint| and projects it from 2D mouse coordinates onto the + * virtual track ball that was set up in startAtPointInFrame method. + * Returns a quaternion that represents the rotation from |rollStart_| to + * |rollEnd_|. + * @param {!Object.<x, y>} dragPoint 2D-point representing the + * destination mouse point. + * @return {Array.<number>} a quaternion that represents the rotation from + * the point wnere the mouse was clicked on the trackball to this point. + * The quaternion looks like this: [[v], cos(angle/2)], where [v] is the + * imaginary part of the quaternion and is computed as [x, y, z] * + * sin(angle/2). + */ +tumbler.Trackball.prototype.rollToPoint = function(dragPoint) { + var rollTo = this.convertClientPoint_(dragPoint); + if ((Math.abs(this.rollStart_.x - rollTo.x) < + tumbler.Trackball.DOUBLE_EPSILON) && + (Math.abs(this.rollStart_.y, rollTo.y) < + tumbler.Trackball.DOUBLE_EPSILON)) { + // Not enough change in the vectors to roll the ball, return the identity + // quaternion. + return [0, 0, 0, 1]; + } + + // Compute the ending vector from the surface of the ball to its center. + var rollEnd = this.projectOnTrackball_(rollTo); + + // Take the cross product of the two vectors. r = s X e + var rollVector = this.rollStart_.cross(rollEnd); + var invStartMag = 1.0 / this.rollStart_.magnitude(); + var invEndMag = 1.0 / rollEnd.magnitude(); + + // cos(a) = (s . e) / (||s|| ||e||) + var cosAng = this.rollStart_.dot(rollEnd) * invStartMag * invEndMag; + // sin(a) = ||(s X e)|| / (||s|| ||e||) + var sinAng = rollVector.magnitude() * invStartMag * invEndMag; + // Build a quaternion that represents the rotation about |rollVector|. + // Use atan2 for a better angle. If you use only cos or sin, you only get + // half the possible angles, and you can end up with rotations that flip + // around near the poles. + var rollHalfAngle = Math.atan2(sinAng, cosAng) * 0.5; + rollVector.normalize(); + // The quaternion looks like this: [[v], cos(angle/2)], where [v] is the + // imaginary part of the quaternion and is computed as [x, y, z] * + // sin(angle/2). + rollVector.scale(Math.sin(rollHalfAngle)); + var ballQuaternion = [rollVector.x, + rollVector.y, + rollVector.z, + Math.cos(rollHalfAngle)]; + return ballQuaternion; +}; + +/** + * Handle the drag START event: grab the current camera orientation from the + * sending view and set up the virtual trackball. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragStartEvent The DRAG_START event that + * triggered this handler. + */ +tumbler.Trackball.prototype.handleStartDrag = + function(controller, dragStartEvent) { + // Cache the camera orientation. The orientations from the trackball as it + // rolls are concatenated to this orientation and pushed back into the + // plugin on the other side of the JavaScript bridge. + controller.setCameraOrientation(this.cameraOrientation_); + // Invert the y-coordinate for the trackball computations. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragStartEvent.clientX, + y: frameSize.height - dragStartEvent.clientY }; + this.startAtPointInFrame(flippedY, frameSize); +}; + +/** + * Handle the drag DRAG event: concatenate the current orientation to the + * cached orientation. Send this final value through to the GSPlugin via the + * setValueForKey() method. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragEvent The DRAG event that triggered this + * handler. + */ +tumbler.Trackball.prototype.handleDrag = + function(controller, dragEvent) { + // Flip the y-coordinate so that the 2D origin is in the lower-left corner. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragEvent.clientX, + y: frameSize.height - dragEvent.clientY }; + controller.setCameraOrientation( + tumbler.multQuaternions(this.rollToPoint(flippedY), + this.cameraOrientation_)); +}; + +/** + * Handle the drag END event: get the final orientation and concatenate it to + * the cached orientation. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragEndEvent The DRAG_END event that triggered + * this handler. + */ +tumbler.Trackball.prototype.handleEndDrag = + function(controller, dragEndEvent) { + // Flip the y-coordinate so that the 2D origin is in the lower-left corner. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragEndEvent.clientX, + y: frameSize.height - dragEndEvent.clientY }; + this.cameraOrientation_ = tumbler.multQuaternions(this.rollToPoint(flippedY), + this.cameraOrientation_); + controller.setCameraOrientation(this.cameraOrientation_); +}; + +/** + * A utility function to multiply two quaterions. Returns the product q0 * q1. + * This is effectively the same thing as concatenating the two rotations + * represented in each quaternion together. Note that quaternion multiplication + * is NOT commutative: q0 * q1 != q1 * q0. + * @param {!Array.<number>} q0 A 4-element array representing the first + * quaternion. + * @param {!Array.<number>} q1 A 4-element array representing the second + * quaternion. + * @return {Array.<number>} A 4-element array representing the product q0 * q1. + */ +tumbler.multQuaternions = function(q0, q1) { + // Return q0 * q1 (note the order). + var qMult = [ + q0[3] * q1[0] + q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1], + q0[3] * q1[1] - q0[0] * q1[2] + q0[1] * q1[3] + q0[2] * q1[0], + q0[3] * q1[2] + q0[0] * q1[1] - q0[1] * q1[0] + q0[2] * q1[3], + q0[3] * q1[3] - q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] + ]; + return qMult; +}; + +/** + * Real numbers that are less than this distance apart are considered + * equivalent. + * TODO(dspringer): It seems as though there should be a const like this + * in Closure somewhere (goog.math?). + * @type {number} + */ +tumbler.Trackball.DOUBLE_EPSILON = 1.0e-16; diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/transforms.cc b/native_client_sdk/src/examples/fullscreen_tumbler/transforms.cc new file mode 100644 index 0000000..609f8fd --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/transforms.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/fullscreen_tumbler/transforms.h" + +#include <GLES2/gl2.h> +#include <math.h> +#include <string.h> + +namespace transform_4x4 { + +static const GLfloat kPI = 3.1415926535897932384626433832795f; + +void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz) { + m[12] += (m[0] * tx + m[4] * ty + m[8] * tz); + m[13] += (m[1] * tx + m[5] * ty + m[9] * tz); + m[14] += (m[2] * tx + m[6] * ty + m[10] * tz); + m[15] += (m[3] * tx + m[7] * ty + m[11] * tz); +} + +void Frustum(GLfloat* m, + GLfloat left, + GLfloat right, + GLfloat bottom, + GLfloat top, + GLfloat near_z, + GLfloat far_z) { + GLfloat delta_x = right - left; + GLfloat delta_y = top - bottom; + GLfloat delta_z = far_z - near_z; + GLfloat frustum[16]; + + if ((near_z <= 0.0f) || (far_z <= 0.0f) || + (delta_x <= 0.0f) || (delta_y <= 0.0f) || (delta_z <= 0.0f)) + return; + + frustum[0] = 2.0f * near_z / delta_x; + frustum[1] = frustum[2] = frustum[3] = 0.0f; + + frustum[5] = 2.0f * near_z / delta_y; + frustum[4] = frustum[6] = frustum[7] = 0.0f; + + frustum[8] = (right + left) / delta_x; + frustum[9] = (top + bottom) / delta_y; + frustum[10] = -(near_z + far_z) / delta_z; + frustum[11] = -1.0f; + + frustum[14] = -2.0f * near_z * far_z / delta_z; + frustum[12] = frustum[13] = frustum[15] = 0.0f; + + transform_4x4::Multiply(m, frustum, m); +} + + +void Perspective(GLfloat* m, + GLfloat fovy, + GLfloat aspect, + GLfloat near_z, + GLfloat far_z) { + GLfloat frustum_w, frustum_h; + + frustum_h = tanf((fovy * 0.5f) / 180.0f * kPI) * near_z; + frustum_w = frustum_h * aspect; + transform_4x4::Frustum(m, -frustum_w, frustum_w, -frustum_h, frustum_h, + near_z, far_z); +} + +void Multiply(GLfloat *m, GLfloat *a, GLfloat* b) { + GLfloat tmp[16]; + // tmp = a . b + GLfloat a0, a1, a2, a3; + a0 = a[0]; + a1 = a[1]; + a2 = a[2]; + a3 = a[3]; + tmp[0] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[1] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[2] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[3] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[4]; + a1 = a[5]; + a2 = a[6]; + a3 = a[7]; + tmp[4] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[5] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[6] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[7] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[8]; + a1 = a[9]; + a2 = a[10]; + a3 = a[11]; + tmp[8] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[9] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[10] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[11] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[12]; + a1 = a[13]; + a2 = a[14]; + a3 = a[15]; + tmp[12] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[13] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[14] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[15] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + memcpy(m, tmp, sizeof(GLfloat) * 4 * 4); +} + +void LoadIdentity(GLfloat* m) { + memset(m, 0, sizeof(GLfloat) * 4 * 4); + m[0] = m[5] = m[10] = m[15] = 1.0f; +} + +} // namespace transform_4x4 + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/transforms.h b/native_client_sdk/src/examples/fullscreen_tumbler/transforms.h new file mode 100644 index 0000000..5ac3d6e --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/transforms.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_TRANSFORMS_H_ +#define EXAMPLES_TUMBLER_TRANSFORMS_H_ + +#include <GLES2/gl2.h> + +// A very simple set of 4x4 matrix routines. In all these routines, the input +// matrix is assumed to be a 4x4 of GLfloats. + +namespace transform_4x4 { + +// Pre-multply |m| with a projection transformation 4x4 matrix from a +// truncated pyramid viewing frustum. +void Frustum(GLfloat* m, + GLfloat left, + GLfloat right, + GLfloat bottom, + GLfloat top, + GLfloat near_z, + GLfloat far_z); + +// Replace |m| with the 4x4 identity matrix. +void LoadIdentity(GLfloat* m); + +// |m| <- |a| . |b|. |m| can point at the same memory as either |a| or |b|. +void Multiply(GLfloat *m, GLfloat *a, GLfloat* b); + +// Pre-multiply |m| with a single-point perspective matrix based on the viewing +// frustum whose view angle is |fovy|. +void Perspective(GLfloat* m, + GLfloat fovy, + GLfloat aspect, + GLfloat near_z, + GLfloat far_z); + +// Pre-multiply |m| with a matrix that represents a translation by |tx|, |ty|, +// |tz|. +void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz); +} // namespace transform_4x4 + +#endif // EXAMPLES_TUMBLER_TRANSFORMS_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.cc b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.cc new file mode 100644 index 0000000..3c38639 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.cc @@ -0,0 +1,202 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/fullscreen_tumbler/tumbler.h" + +#include <stdio.h> + +#include <cstdlib> +#include <cstring> +#include <string> +#include <vector> + +#include "examples/fullscreen_tumbler/cube.h" +#include "examples/fullscreen_tumbler/opengl_context.h" +#include "examples/fullscreen_tumbler/scripting_bridge.h" +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/size.h" +#include "ppapi/cpp/var.h" + +namespace { +const uint32_t kKeyEnter = 0x0D; +const size_t kQuaternionElementCount = 4; +const char* const kArrayStartCharacter = "["; +const char* const kArrayEndCharacter = "]"; +const char* const kArrayDelimiter = ","; + +// Return the value of parameter named |param_name| from |parameters|. If +// |param_name| doesn't exist, then return an empty string. +std::string GetParameterNamed( + const std::string& param_name, + const tumbler::MethodParameter& parameters) { + tumbler::MethodParameter::const_iterator i = + parameters.find(param_name); + if (i == parameters.end()) { + return ""; + } + return i->second; +} + +// Convert the JSON string |array| into a vector of floats. |array| is +// expected to be a string bounded by '[' and ']' and containing a +// comma-delimited list of numbers. Any errors result in the return of an +// empty array. +std::vector<float> CreateArrayFromJSON(const std::string& json_array) { + std::vector<float> float_array; + size_t array_start_pos = json_array.find_first_of(kArrayStartCharacter); + size_t array_end_pos = json_array.find_last_of(kArrayEndCharacter); + if (array_start_pos == std::string::npos || + array_end_pos == std::string::npos) + return float_array; // Malformed JSON: missing '[' or ']'. + // Pull out the array elements. + size_t token_pos = array_start_pos + 1; + while (token_pos < array_end_pos) { + float_array.push_back(strtof(json_array.data() + token_pos, NULL)); + size_t delim_pos = json_array.find_first_of(kArrayDelimiter, token_pos); + if (delim_pos == std::string::npos) + break; + token_pos = delim_pos + 1; + } + return float_array; +} +} // namespace + +namespace tumbler { + +Tumbler::Tumbler(PP_Instance instance) + : pp::Instance(instance), + full_screen_(this), + has_focus_(false), + cube_(NULL) { + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE); + RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); +} + +Tumbler::~Tumbler() { + // Destroy the cube view while GL context is current. + opengl_context_->MakeContextCurrent(this); + delete cube_; +} + +bool Tumbler::Init(uint32_t /* argc */, + const char* /* argn */[], + const char* /* argv */[]) { + // Add all the methods to the scripting bridge. + ScriptingBridge::SharedMethodCallbackExecutor set_orientation_method( + new tumbler::MethodCallback<Tumbler>( + this, &Tumbler::SetCameraOrientation)); + scripting_bridge_.AddMethodNamed("setCameraOrientation", + set_orientation_method); + return true; +} + +void Tumbler::HandleMessage(const pp::Var& message) { + if (!message.is_string()) + return; + scripting_bridge_.InvokeMethod(message.AsString()); +} + +bool Tumbler::HandleInputEvent(const pp::InputEvent& event) { + switch (event.GetType()) { + case PP_INPUTEVENT_TYPE_UNDEFINED: + break; + case PP_INPUTEVENT_TYPE_MOUSEDOWN: + // If we do not yet have focus, return true. In return Chrome will give + // focus to the NaCl embed. + return !has_focus_; + break; + case PP_INPUTEVENT_TYPE_KEYDOWN: + HandleKeyDownEvent(pp::KeyboardInputEvent(event)); + break; + case PP_INPUTEVENT_TYPE_MOUSEUP: + case PP_INPUTEVENT_TYPE_MOUSEMOVE: + case PP_INPUTEVENT_TYPE_MOUSEENTER: + case PP_INPUTEVENT_TYPE_MOUSELEAVE: + case PP_INPUTEVENT_TYPE_WHEEL: + case PP_INPUTEVENT_TYPE_RAWKEYDOWN: + case PP_INPUTEVENT_TYPE_KEYUP: + case PP_INPUTEVENT_TYPE_CHAR: + case PP_INPUTEVENT_TYPE_CONTEXTMENU: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: + case PP_INPUTEVENT_TYPE_IME_TEXT: + default: + return false; + } + return false; +} + +void Tumbler::DidChangeView(const pp::Rect& position, const pp::Rect& clip) { + // Note: When switching to fullscreen, the new View position will be a + // rectangle that encompasses the entire screen - e.g. 1900x1200 - with its + // top-left corner at (0, 0). When switching back to the windowed screen the + // position returns to what it was before going to fullscreen. + int cube_width = cube_ ? cube_->width() : 0; + int cube_height = cube_ ? cube_->height() : 0; + if (position.size().width() == cube_width && + position.size().height() == cube_height) { + return; // Size didn't change, no need to update anything. + } + + if (opengl_context_ == NULL) + opengl_context_.reset(new OpenGLContext(this)); + opengl_context_->InvalidateContext(this); + opengl_context_->ResizeContext(position.size()); + if (!opengl_context_->MakeContextCurrent(this)) + return; + if (cube_ == NULL) { + cube_ = new Cube(opengl_context_); + cube_->PrepareOpenGL(); + } + cube_->Resize(position.size().width(), position.size().height()); + DrawSelf(); +} + +void Tumbler::DidChangeFocus(bool focus) { + has_focus_ = focus; +} + +void Tumbler::DrawSelf() { + if (cube_ == NULL || opengl_context_ == NULL) + return; + opengl_context_->MakeContextCurrent(this); + cube_->Draw(); + opengl_context_->FlushContext(); +} + +void Tumbler::HandleKeyDownEvent(const pp::KeyboardInputEvent& key_event) { + // Pressing the Enter key toggles the view to/from full screen. + if (key_event.GetKeyCode() == kKeyEnter) { + if (!full_screen_.IsFullscreen()) { + if (!full_screen_.SetFullscreen(true)) { + printf("Failed to switch to fullscreen mode.\n"); + } + } else { + if (!full_screen_.SetFullscreen(false)) { + printf("Failed to switch to normal mode.\n"); + } + } + } +} + +void Tumbler::SetCameraOrientation( + const tumbler::ScriptingBridge& bridge, + const tumbler::MethodParameter& parameters) { + // |parameters| is expected to contain one object named "orientation", whose + // value is a JSON string that represents an array of four floats. + if (parameters.size() != 1 || cube_ == NULL) + return; + std::string orientation_desc = GetParameterNamed("orientation", parameters); + std::vector<float> orientation = CreateArrayFromJSON(orientation_desc); + if (orientation.size() != kQuaternionElementCount) { + return; + } + cube_->SetOrientation(orientation); + DrawSelf(); +} + +} // namespace tumbler + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.h b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.h new file mode 100644 index 0000000..d752ea3 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.h @@ -0,0 +1,83 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_TUMBLER_H_ +#define EXAMPLES_TUMBLER_TUMBLER_H_ + +#include <pthread.h> +#include <map> +#include <vector> + +#include "examples/fullscreen_tumbler/cube.h" +#include "examples/fullscreen_tumbler/opengl_context.h" +#include "examples/fullscreen_tumbler/opengl_context_ptrs.h" +#include "examples/fullscreen_tumbler/scripting_bridge.h" +#include "ppapi/cpp/fullscreen.h" +#include "ppapi/cpp/instance.h" + +namespace pp { +class KeyboardInputEvent; +} // namespace pp + + +namespace tumbler { + +class Tumbler : public pp::Instance { + public: + explicit Tumbler(PP_Instance instance); + + // The dtor makes the 3D context current before deleting the cube view, then + // destroys the 3D context both in the module and in the browser. + virtual ~Tumbler(); + + // Called by the browser when the NaCl module is loaded and all ready to go. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Called whenever the in-browser window changes size. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip); + + // Called by the browser when the NaCl canvas gets or loses focus. + virtual void DidChangeFocus(bool has_focus); + + // Called by the browser to handle the postMessage() call in Javascript. + virtual void HandleMessage(const pp::Var& message); + + // Called by the browser to handle incoming input events. + virtual bool HandleInputEvent(const pp::InputEvent& event); + + // Bind and publish the module's methods to JavaScript. + void InitializeMethods(ScriptingBridge* bridge); + + // Set the camera orientation to the quaternion in |args[0]|. |args| must + // have length at least 1; the first element is expeted to be an Array + // object containing 4 floating point number elements (the quaternion). + // This method is bound to the JavaScript "setCameraOrientation" method and + // is called like this: + // module.setCameraOrientation([0.0, 1.0, 0.0, 0.0]); + void SetCameraOrientation( + const tumbler::ScriptingBridge& bridge, + const tumbler::MethodParameter& parameters); + + // Called to draw the contents of the module's browser area. + void DrawSelf(); + + private: + // Process key-down input events. + void HandleKeyDownEvent(const pp::KeyboardInputEvent& key_event); + + pp::Fullscreen full_screen_; + bool has_focus_; + + // Browser connectivity and scripting support. + ScriptingBridge scripting_bridge_; + + SharedOpenGLContext opengl_context_; + // Wouldn't it be awesome if we had boost::scoped_ptr<>? + Cube* cube_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_TUMBLER_H_ + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.js b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.js new file mode 100644 index 0000000..58096e4 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.js @@ -0,0 +1,133 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview The tumbler Application object. This object instantiates a + * Trackball object and connects it to the element named |tumbler_content|. + * It also conditionally embeds a debuggable module or a release module into + * the |tumbler_content| element. + */ + +// Requires tumbler +// Requires tumbler.Dragger +// Requires tumbler.Trackball + +/** + * Constructor for the Application class. Use the run() method to populate + * the object with controllers and wire up the events. + * @constructor + */ +tumbler.Application = function() { + /** + * The native module for the application. This refers to the module loaded + * using the <embed> tag. + * @type {Element} + * @private + */ + this.module_ = null; + + /** + * The trackball object. + * @type {tumbler.Trackball} + * @private + */ + this.trackball_ = null; + + /** + * The mouse-drag event object. + * @type {tumbler.Dragger} + * @private + */ + this.dragger_ = null; + + /** + * The function objects that get attached as event handlers. These are + * cached so that they can be removed when they are no longer needed. + * @type {function} + * @private + */ + this.boundModuleDidLoad_ = null; +} + +/** + * The ids used for elements in the DOM. The Tumbler Application expects these + * elements to exist. + * @enum {string} + * @private + */ +tumbler.Application.DomIds_ = { + MODULE: 'tumbler', // The <embed> element representing the NaCl module + VIEW: 'tumbler_view' // The <div> containing the NaCl element. +} + +/** + * Called by the module loading function once the module has been loaded. + * @param {?Element} nativeModule The instance of the native module. + */ +tumbler.Application.prototype.moduleDidLoad = function() { + this.module_ = document.getElementById(tumbler.Application.DomIds_.MODULE); + // Unbind the load function. + this.boundModuleDidLoad_ = null; + + /** + * Set the camera orientation property on the NaCl module. + * @param {Array.<number>} orientation A 4-element array representing the + * camera orientation as a quaternion. + */ + this.module_.setCameraOrientation = function(orientation) { + var methodString = 'setCameraOrientation ' + + 'orientation:' + + JSON.stringify(orientation); + this.postMessage(methodString); + } + + this.trackball_ = new tumbler.Trackball(); + this.dragger_ = new tumbler.Dragger(this.module_); + this.dragger_.addDragListener(this.trackball_); +} + +/** + * Asserts that cond is true; issues an alert and throws an Error otherwise. + * @param {bool} cond The condition. + * @param {String} message The error message issued if cond is false. + */ +tumbler.Application.prototype.assert = function(cond, message) { + if (!cond) { + message = "Assertion failed: " + message; + alert(message); + throw new Error(message); + } +} + +/** + * The run() method starts and 'runs' the application. The trackball object + * is allocated and all the events get wired up. + * @param {?String} opt_contentDivName The id of a DOM element in which to + * embed the Native Client module. If unspecified, defaults to + * VIEW. The DOM element must exist. + */ +tumbler.Application.prototype.run = function(opt_contentDivName) { + contentDivName = opt_contentDivName || tumbler.Application.DomIds_.VIEW; + var contentDiv = document.getElementById(contentDivName); + this.assert(contentDiv, "Missing DOM element '" + contentDivName + "'"); + + // Note that the <EMBED> element is wrapped inside a <DIV>, which has a 'load' + // event listener attached. This method is used instead of attaching the + // 'load' event listener directly to the <EMBED> element to ensure that the + // listener is active before the NaCl module 'load' event fires. + this.boundModuleDidLoad_ = this.moduleDidLoad.bind(this); + contentDiv.addEventListener('load', this.boundModuleDidLoad_, true); + + // Load the published .nexe. This includes the 'nacl' attribute which + // shows how to load multi-architecture modules. Each entry in the "nexes" + // object in the .nmf manifest file is a key-value pair: the key is the + // runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired + // NaCl module. To load the debug versions of your .nexes, set the 'nacl' + // attribute to the _dbg.nmf version of the manifest file. + contentDiv.innerHTML = '<embed id="' + + tumbler.Application.DomIds_.MODULE + '" ' + + 'src=fullscreen_tumbler.nmf ' + + 'type="application/x-nacl" ' + + 'width="480" height="480" />' +} diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/tumbler_module.cc b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler_module.cc new file mode 100644 index 0000000..37e8bd0 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler_module.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/fullscreen_tumbler/tumbler.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/gles2/gl2ext_ppapi.h" + +/// The Module class. The browser calls the CreateInstance() method to create +/// an instance of your NaCl module on the web page. The browser creates a new +/// instance for each <embed> tag with type="application/x-nacl". +class TumberModule : public pp::Module { + public: + TumberModule() : pp::Module() {} + virtual ~TumberModule() { + glTerminatePPAPI(); + } + + /// Called by the browser when the module is first loaded and ready to run. + /// This is called once per module, not once per instance of the module on + /// the page. + virtual bool Init() { + return glInitializePPAPI(get_browser_interface()) == GL_TRUE; + } + + /// Create and return a Tumbler instance object. + /// @param[in] instance The browser-side instance. + /// @return the plugin-side instance. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new tumbler::Tumbler(instance); + } +}; + +namespace pp { +/// Factory function called by the browser when the module is first loaded. +/// The browser keeps a singleton of this module. It calls the +/// CreateInstance() method on the object you return to make instances. There +/// is one instance per <embed> tag on the page. This is the main binding +/// point for your NaCl module with the browser. +Module* CreateModule() { + return new TumberModule(); +} +} // namespace pp + diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/vector3.js b/native_client_sdk/src/examples/fullscreen_tumbler/vector3.js new file mode 100644 index 0000000..a79f781 --- /dev/null +++ b/native_client_sdk/src/examples/fullscreen_tumbler/vector3.js @@ -0,0 +1,91 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview A 3D vector class. Proviudes some utility functions on + * 3-dimentional vectors. + */ + +// Requires tumbler + +/** + * Constructor for the Vector3 object. This class contains a 3-tuple that + * represents a vector in 3D space. + * @param {?number} opt_x The x-coordinate for this vector. If null or + * undefined, the x-coordinate value is set to 0. + * @param {?number} opt_y The y-coordinate for this vector. If null or + * undefined, the y-coordinate value is set to 0. + * @param {?number} opt_z The z-coordinate for this vector. If null or + * undefined, the z-coordinate value is set to 0. + * @constructor + */ +tumbler.Vector3 = function(opt_x, opt_y, opt_z) { + /** + * The vector's 3-tuple. + * @type {number} + */ + this.x = opt_x || 0; + this.y = opt_y || 0; + this.z = opt_z || 0; +} + +/** + * Method to return the magnitude of a Vector3. + * @return {number} the magnitude of the vector. + */ +tumbler.Vector3.prototype.magnitude = function() { + return Math.sqrt(this.dot(this)); +} + +/** + * Normalize the vector in-place. + * @return {number} the magnitude of the vector. + */ +tumbler.Vector3.prototype.normalize = function() { + var mag = this.magnitude(); + if (mag < tumbler.Vector3.DOUBLE_EPSILON) + return 0.0; // |this| is equivalent to the 0-vector, don't normalize. + this.scale(1.0 / mag); + return mag; +} + +/** + * Scale the vector in-place by |s|. + * @param {!number} s The scale factor. + */ +tumbler.Vector3.prototype.scale = function(s) { + this.x *= s; + this.y *= s; + this.z *= s; +} + +/** + * Compute the dot product: |this| . v. + * @param {!tumbler.Vector3} v The vector to dot. + * @return {number} the result of |this| . v. + */ +tumbler.Vector3.prototype.dot = function(v) { + return this.x * v.x + this.y * v.y + this.z * v.z; +} + +/** + * Compute the cross product: |this| X v. + * @param {!tumbler.Vector3} v The vector to cross with. + * @return {tumbler.Vector3} the result of |this| X v. + */ +tumbler.Vector3.prototype.cross = function(v) { + var vCross = new tumbler.Vector3(this.y * v.z - this.z * v.y, + this.z * v.x - this.x * v.z, + this.x * v.y - this.y * v.x); + return vCross; +} + +/** + * Real numbers that are less than this distance apart are considered + * equivalent. + * TODO(dspringer): It seems as though there should be a const like this + * in generally available somewhere. + * @type {number} + */ +tumbler.Vector3.DOUBLE_EPSILON = 1.0e-16; diff --git a/native_client_sdk/src/examples/geturl/build.scons b/native_client_sdk/src/examples/geturl/build.scons new file mode 100644 index 0000000..f855644 --- /dev/null +++ b/native_client_sdk/src/examples/geturl/build.scons @@ -0,0 +1,41 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='geturl', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['geturl.cc', 'geturl_handler.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'geturl') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('geturl') + +app_files = [ + 'geturl.html', + 'geturl_success.html', + 'geturl.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/geturl/geturl.cc b/native_client_sdk/src/examples/geturl/geturl.cc new file mode 100644 index 0000000..48d5cbb --- /dev/null +++ b/native_client_sdk/src/examples/geturl/geturl.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This example demonstrates how to load content of the page into NaCl module. + +#include <cstdio> +#include <string> +#include "examples/geturl/geturl_handler.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/url_loader.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" + +// These are the method names as JavaScript sees them. +namespace { +const char* const kLoadUrlMethodId = "getUrl"; +static const char kMessageArgumentSeparator = ':'; + +// Exception strings. These are passed back to the browser when errors +// happen during property accesses or method calls. +const char* const kExceptionStartFailed = "GetURLHandler::Start() failed"; +const char* const kExceptionURLNotAString = "URL is not a string"; +} // namespace + +// The Instance class. One of these exists for each instance of your NaCl +// module on the web page. The browser will ask the Module object to create +// a new Instance for each occurrence of the <embed> tag that has these +// attributes: +// type="application/x-nacl" +// src="geturl.nmf" +class GetURLInstance : public pp::Instance { + public: + explicit GetURLInstance(PP_Instance instance) : pp::Instance(instance) {} + virtual ~GetURLInstance() {} + + // Called by the browser to handle the postMessage() call in Javascript. + // The message in this case is expected to contain the string 'getUrl' + // followed by a ':' separator, then the URL to fetch. If a valid message + // of the form 'getUrl:URL' is received, then start up an asynchronous + // download of URL. In the event that errors occur, this method posts an + // error string back to the browser. + virtual void HandleMessage(const pp::Var& var_message); +}; + +void GetURLInstance::HandleMessage(const pp::Var& var_message) { + if (!var_message.is_string()) { + return; + } + std::string message = var_message.AsString(); + if (message.find(kLoadUrlMethodId) == 0) { + // The argument to getUrl is everything after the first ':'. + size_t sep_pos = message.find_first_of(kMessageArgumentSeparator); + if (sep_pos != std::string::npos) { + std::string url = message.substr(sep_pos + 1); + printf("GetURLInstance::HandleMessage('%s', '%s')\n", + message.c_str(), + url.c_str()); + fflush(stdout); + GetURLHandler* handler = GetURLHandler::Create(this, url); + if (handler != NULL) { + // Starts asynchronous download. When download is finished or when an + // error occurs, |handler| posts the results back to the browser + // vis PostMessage and self-destroys. + handler->Start(); + } + } + } +} + + +// The Module class. The browser calls the CreateInstance() method to create +// an instance of you NaCl module on the web page. The browser creates a new +// instance for each <embed> tag with type="application/x-nacl". +class GetURLModule : public pp::Module { + public: + GetURLModule() : pp::Module() {} + virtual ~GetURLModule() {} + + // Create and return a GetURLInstance object. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new GetURLInstance(instance); + } +}; + +// Factory function called by the browser when the module is first loaded. +// The browser keeps a singleton of this module. It calls the +// CreateInstance() method on the object you return to make instances. There +// is one instance per <embed> tag on the page. This is the main binding +// point for your NaCl module with the browser. +namespace pp { +Module* CreateModule() { + return new GetURLModule(); +} +} // namespace pp + diff --git a/native_client_sdk/src/examples/geturl/geturl.html b/native_client_sdk/src/examples/geturl/geturl.html new file mode 100644 index 0000000..c4a0c79 --- /dev/null +++ b/native_client_sdk/src/examples/geturl/geturl.html @@ -0,0 +1,125 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> +<head> + <title>Get URL</title> + + <script type="text/javascript"> + geturlModule = null; // Global application object. + statusText = 'NO-STATUS'; + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + geturlModule = document.getElementById('geturl'); + updateStatus('SUCCESS'); + } + + // If the page loads before the Native Client module loads, then set the + // status message indicating that the module is still loading. Otherwise, + // do not change the status message. + function pageDidLoad() { + if (geturl == null) { + updateStatus('LOADING...'); + } else { + // It's possible that the Native Client module onload event fired + // before the page's onload event. In this case, the status message + // will reflect 'SUCCESS', but won't be displayed. This call will + // display the current message. + updateStatus(); + } + } + + // Called from the NaCl module via PostMessage(). The message data + // contains a URL followed by a '\n' separator character and the result + // text. The result test itself can contain '\n' characters, only the first + // '\n' is considered when separating the message parameters. + function handleMessage(message_event) { + var logElt = document.getElementById('general_output'); + // Find the first line break. This separates the URL data from the + // result text. Note that the result text can contain any number of + // '\n' characters, so split() won't work here. + var url = message_event.data; + var result = ''; + var eol_pos = message_event.data.indexOf("\n"); + if (eol_pos != -1) { + url = message_event.data.substring(0, eol_pos); + if (eol_pos < message_event.data.length - 1) { + result = message_event.data.substring(eol_pos + 1); + } + } + logElt.textContent += 'FULLY QUALIFIED URL: ' + url + '\n'; + logElt.textContent += 'RESULT:\n' + result + '\n'; + } + + function loadUrl() { + geturlModule.postMessage('getUrl:geturl_success.html'); + } + + // Set the global status message. If the element with id 'statusField' + // exists, then set its HTML to the status message as well. + // @param opt_message The message text. If this is null or undefined, then + // attempt to set the element with id 'status_field' to the value of + // @a statusText. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + var statusField = document.getElementById('status_field'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + </script> +</head> +<body onload="pageDidLoad()"> + +<h1>Native Client GetURL Module</h1> +<p> +<table border=5 cellpadding=5% summary="A title and a result log"> + <tr> + <td valign=top><pre id='general_output' class='notrun'></pre></td> + </tr> +</table> + + <form name="geturl_form" action="" method="get"> + <input type="button" value="Get URL" onclick="loadUrl()"/> + </form> + + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="geturl" + width=0 height=0 + src="geturl.nmf" + type="application/x-nacl" /> + </div> +</p> + +<h2>Module loading status</h2> +<div id="status_field">NO-STATUS</div> +</body> +</html> diff --git a/native_client_sdk/src/examples/geturl/geturl_handler.cc b/native_client_sdk/src/examples/geturl/geturl_handler.cc new file mode 100644 index 0000000..f6536fc --- /dev/null +++ b/native_client_sdk/src/examples/geturl/geturl_handler.cc @@ -0,0 +1,137 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/geturl/geturl_handler.h" + +#include <stdio.h> +#include <stdlib.h> +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/ppb_instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" + +namespace { +bool IsError(int32_t result) { + return ((PP_OK != result) && (PP_OK_COMPLETIONPENDING != result)); +} +} // namespace + +GetURLHandler* GetURLHandler::Create(pp::Instance* instance, + const std::string& url) { + return new GetURLHandler(instance, url); +} + +GetURLHandler::GetURLHandler(pp::Instance* instance, + const std::string& url) + : instance_(instance), + url_(url), + url_request_(instance), + url_loader_(instance), + cc_factory_(this) { + url_request_.SetURL(url); + url_request_.SetMethod("GET"); +} + +GetURLHandler::~GetURLHandler() { +} + +void GetURLHandler::Start() { + pp::CompletionCallback cc = + cc_factory_.NewRequiredCallback(&GetURLHandler::OnOpen); + url_loader_.Open(url_request_, cc); +} + +void GetURLHandler::OnOpen(int32_t result) { + if (result != PP_OK) { + ReportResultAndDie(url_, "pp::URLLoader::Open() failed", false); + return; + } + // Here you would process the headers. A real program would want to at least + // check the HTTP code and potentially cancel the request. + // pp::URLResponseInfo response = loader_.GetResponseInfo(); + + // Start streaming. + ReadBody(); +} + +void GetURLHandler::AppendDataBytes(const char* buffer, int32_t num_bytes) { + if (num_bytes <= 0) + return; + // Make sure we don't get a buffer overrun. + num_bytes = std::min(READ_BUFFER_SIZE, num_bytes); + url_response_body_.reserve(url_response_body_.size() + num_bytes); + url_response_body_.insert(url_response_body_.end(), + buffer, + buffer + num_bytes); +} + +void GetURLHandler::OnRead(int32_t result) { + if (result == PP_OK) { + // Streaming the file is complete. + ReportResultAndDie(url_, url_response_body_, true); + } else if (result > 0) { + // The URLLoader just filled "result" number of bytes into our buffer. + // Save them and perform another read. + AppendDataBytes(buffer_, result); + ReadBody(); + } else { + // A read error occurred. + ReportResultAndDie(url_, + "pp::URLLoader::ReadResponseBody() result<0", + false); + } +} + +void GetURLHandler::ReadBody() { + // Note that you specifically want an "optional" callback here. This will + // allow ReadBody() to return synchronously, ignoring your completion + // callback, if data is available. For fast connections and large files, + // reading as fast as we can will make a large performance difference + // However, in the case of a synchronous return, we need to be sure to run + // the callback we created since the loader won't do anything with it. + pp::CompletionCallback cc = + cc_factory_.NewOptionalCallback(&GetURLHandler::OnRead); + int32_t result = PP_OK; + do { + result = url_loader_.ReadResponseBody(buffer_, sizeof(buffer_), cc); + // Handle streaming data directly. Note that we *don't* want to call + // OnRead here, since in the case of result > 0 it will schedule + // another call to this function. If the network is very fast, we could + // end up with a deeply recursive stack. + if (result > 0) { + AppendDataBytes(buffer_, result); + } + } while (result > 0); + + if (result != PP_OK_COMPLETIONPENDING) { + // Either we reached the end of the stream (result == PP_OK) or there was + // an error. We want OnRead to get called no matter what to handle + // that case, whether the error is synchronous or asynchronous. If the + // result code *is* COMPLETIONPENDING, our callback will be called + // asynchronously. + cc.Run(result); + } +} + +void GetURLHandler::ReportResultAndDie(const std::string& fname, + const std::string& text, + bool success) { + ReportResult(fname, text, success); + delete this; +} + +void GetURLHandler::ReportResult(const std::string& fname, + const std::string& text, + bool success) { + if (success) + printf("GetURLHandler::ReportResult(Ok).\n"); + else + printf("GetURLHandler::ReportResult(Err). %s\n", text.c_str()); + fflush(stdout); + if (instance_) { + pp::Var var_result(fname + "\n" + text); + instance_->PostMessage(var_result); + } +} + diff --git a/native_client_sdk/src/examples/geturl/geturl_handler.h b/native_client_sdk/src/examples/geturl/geturl_handler.h new file mode 100644 index 0000000..928f22c --- /dev/null +++ b/native_client_sdk/src/examples/geturl/geturl_handler.h @@ -0,0 +1,81 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_GETURL_GETURL_HANDLER_H_ +#define EXAMPLES_GETURL_GETURL_HANDLER_H_ + +#include <string> +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/url_loader.h" +#include "ppapi/cpp/url_request_info.h" +#include "ppapi/cpp/instance.h" + +#define READ_BUFFER_SIZE 4096 + +// GetURLHandler is used to download data from |url|. When download is +// finished or when an error occurs, it posts a message back to the browser +// with the results encoded in the message as a string and self-destroys. +// +// EXAMPLE USAGE: +// GetURLHandler* handler* = GetURLHandler::Create(instance,url); +// handler->Start(); +// +class GetURLHandler { + public: + // Creates instance of GetURLHandler on the heap. + // GetURLHandler objects shall be created only on the heap (they + // self-destroy when all data is in). + static GetURLHandler* Create(pp::Instance* instance_, + const std::string& url); + // Initiates page (URL) download. + void Start(); + + private: + GetURLHandler(pp::Instance* instance_, const std::string& url); + ~GetURLHandler(); + + // Callback fo the pp::URLLoader::Open(). + // Called by pp::URLLoader when response headers are received or when an + // error occurs (in response to the call of pp::URLLoader::Open()). + // Look at <ppapi/c/ppb_url_loader.h> and + // <ppapi/cpp/url_loader.h> for more information about pp::URLLoader. + void OnOpen(int32_t result); + + // Callback fo the pp::URLLoader::ReadResponseBody(). + // |result| contains the number of bytes read or an error code. + // Appends data from this->buffer_ to this->url_response_body_. + void OnRead(int32_t result); + + // Reads the response body (asynchronously) into this->buffer_. + // OnRead() will be called when bytes are received or when an error occurs. + void ReadBody(); + + // Append data bytes read from the URL onto the internal buffer. Does + // nothing if |num_bytes| is 0. + void AppendDataBytes(const char* buffer, int32_t num_bytes); + + // Post a message back to the browser with the download results. + void ReportResult(const std::string& fname, + const std::string& text, + bool success); + // Post a message back to the browser with the download results and + // self-destroy. |this| is no longer valid when this method returns. + void ReportResultAndDie(const std::string& fname, + const std::string& text, + bool success); + + pp::Instance* instance_; // Weak pointer. + std::string url_; // URL to be downloaded. + pp::URLRequestInfo url_request_; + pp::URLLoader url_loader_; // URLLoader provides an API to download URLs. + char buffer_[READ_BUFFER_SIZE]; // Temporary buffer for reads. + std::string url_response_body_; // Contains accumulated downloaded data. + pp::CompletionCallbackFactory<GetURLHandler> cc_factory_; + + GetURLHandler(const GetURLHandler&); + void operator=(const GetURLHandler&); +}; + +#endif // EXAMPLES_GETURL_GETURL_HANDLER_H_ + diff --git a/native_client_sdk/src/examples/geturl/geturl_success.html b/native_client_sdk/src/examples/geturl/geturl_success.html new file mode 100644 index 0000000..8f2f112 --- /dev/null +++ b/native_client_sdk/src/examples/geturl/geturl_success.html @@ -0,0 +1,20 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2010 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + <head> + <title>PPAPI geturl example</title> + <META HTTP-EQUIV="Pragma" CONTENT="no-cache" /> + <META HTTP-EQUIV="Expires" CONTENT="-1" /> + </head> + <body> + <h1>PPAPI geturl example</h1> + The PPAPI geturl example fetches the contents of this page. + If you are seeing the contents of this page as part of the test output, + then the test passed. + </body> +</html> diff --git a/native_client_sdk/src/examples/hello_world/build.scons b/native_client_sdk/src/examples/hello_world/build.scons new file mode 100644 index 0000000..51dc5df --- /dev/null +++ b/native_client_sdk/src/examples/hello_world/build.scons @@ -0,0 +1,59 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import nacl_utils +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='hello_world', lib_prefix='..') +nacl_test_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, + nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + use_ppapi=False) +for env in [nacl_env, nacl_test_env]: + env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + CCFLAGS=['-pedantic', '-Werror'], + ) + +sources = ['hello_world.cc', 'helper_functions.cc'] +test_sources = ['helper_functions.cc', 'test_helper_functions.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'hello_world') + +nacl_test_32 = nacl_test_env.Clone() +nacl_test_32.NaClTestProgram(test_sources, + nacl_utils.ARCH_SPECS['x86-32'], + module_name='hello_world_test', + target_name='test32') + +nacl_test_64 = nacl_test_env.Clone() +nacl_test_64.NaClTestProgram(test_sources, + nacl_utils.ARCH_SPECS['x86-64'], + module_name='hello_world_test', + target_name='test64') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('hello_world') + +app_files = [ + 'hello_world.html', + 'hello_world.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/hello_world/hello_world.cc b/native_client_sdk/src/examples/hello_world/hello_world.cc new file mode 100644 index 0000000..df67961 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world/hello_world.cc @@ -0,0 +1,130 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// @file +/// This example demonstrates loading, running and scripting a very simple NaCl +/// module. To load the NaCl module, the browser first looks for the +/// CreateModule() factory method (at the end of this file). It calls +/// CreateModule() once to load the module code from your .nexe. After the +/// .nexe code is loaded, CreateModule() is not called again. +/// +/// Once the .nexe code is loaded, the browser then calls the +/// HelloWorldModule::CreateInstance() +/// method on the object returned by CreateModule(). It calls CreateInstance() +/// each time it encounters an <embed> tag that references your NaCl module. + +#include <cstdio> +#include <cstring> +#include <string> +#include "examples/hello_world/helper_functions.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" + +namespace hello_world { +/// Method name for ReverseText, as seen by JavaScript code. +const char* const kReverseTextMethodId = "reverseText"; + +/// Method name for FortyTwo, as seen by Javascript code. @see FortyTwo() +const char* const kFortyTwoMethodId = "fortyTwo"; + +/// Separator character for the reverseText method. +static const char kMessageArgumentSeparator = ':'; + +/// This is the module's function that invokes FortyTwo and converts the return +/// value from an int32_t to a pp::Var for return. +pp::Var MarshallFortyTwo() { + return pp::Var(FortyTwo()); +} + +/// This function is passed the arg list from the JavaScript call to +/// @a reverseText. +/// It makes sure that there is one argument and that it is a string, returning +/// an error message if it is not. +/// On good input, it calls ReverseText and returns the result. The result is +/// then sent back via a call to PostMessage. +pp::Var MarshallReverseText(const std::string& text) { + return pp::Var(ReverseText(text)); +} + +/// The Instance class. One of these exists for each instance of your NaCl +/// module on the web page. The browser will ask the Module object to create +/// a new Instance for each occurrence of the <embed> tag that has these +/// attributes: +/// <pre> +/// type="application/x-nacl" +/// nacl="hello_world.nmf" +/// </pre> +class HelloWorldInstance : public pp::Instance { + public: + explicit HelloWorldInstance(PP_Instance instance) : pp::Instance(instance) {} + virtual ~HelloWorldInstance() {} + + /// Called by the browser to handle the postMessage() call in Javascript. + /// Detects which method is being called from the message contents, and + /// calls the appropriate function. Posts the result back to the browser + /// asynchronously. + /// @param[in] var_message The message posted by the browser. The possible + /// messages are 'fortyTwo' and 'reverseText:Hello World'. Note that + /// the 'reverseText' form contains the string to reverse following a ':' + /// separator. + virtual void HandleMessage(const pp::Var& var_message); +}; + +void HelloWorldInstance::HandleMessage(const pp::Var& var_message) { + if (!var_message.is_string()) { + return; + } + std::string message = var_message.AsString(); + pp::Var return_var; + if (message == kFortyTwoMethodId) { + // Note that no arguments are passed in to FortyTwo. + return_var = MarshallFortyTwo(); + } else if (message.find(kReverseTextMethodId) == 0) { + // The argument to reverseText is everything after the first ':'. + size_t sep_pos = message.find_first_of(kMessageArgumentSeparator); + if (sep_pos != std::string::npos) { + std::string string_arg = message.substr(sep_pos + 1); + return_var = MarshallReverseText(string_arg); + } + } + // Post the return result back to the browser. Note that HandleMessage() is + // always called on the main thread, so it's OK to post the return message + // directly from here. The return post is asynhronous: PostMessage returns + // immediately. + PostMessage(return_var); +} + +/// The Module class. The browser calls the CreateInstance() method to create +/// an instance of your NaCl module on the web page. The browser creates a new +/// instance for each <embed> tag with +/// <code>type="application/x-nacl"</code>. +class HelloWorldModule : public pp::Module { + public: + HelloWorldModule() : pp::Module() {} + virtual ~HelloWorldModule() {} + + /// Create and return a HelloWorldInstance object. + /// @param[in] instance a handle to a plug-in instance. + /// @return a newly created HelloWorldInstance. + /// @note The browser is responsible for calling @a delete when done. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new HelloWorldInstance(instance); + } +}; +} // namespace hello_world + + +namespace pp { +/// Factory function called by the browser when the module is first loaded. +/// The browser keeps a singleton of this module. It calls the +/// CreateInstance() method on the object you return to make instances. There +/// is one instance per <embed> tag on the page. This is the main binding +/// point for your NaCl module with the browser. +/// @return new HelloWorldModule. +/// @note The browser is responsible for deleting returned @a Module. +Module* CreateModule() { + return new hello_world::HelloWorldModule(); +} +} // namespace pp diff --git a/native_client_sdk/src/examples/hello_world/hello_world.html b/native_client_sdk/src/examples/hello_world/hello_world.html new file mode 100644 index 0000000..16dc42c --- /dev/null +++ b/native_client_sdk/src/examples/hello_world/hello_world.html @@ -0,0 +1,126 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> +<head> + <title>Hello, World!</title> + + <script type="text/javascript"> + helloWorldModule = null; // Global application object. + statusText = 'NO-STATUS'; + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + helloWorldModule = document.getElementById('hello_world'); + updateStatus('SUCCESS'); + } + + // Handle a message coming from the NaCl module. + function handleMessage(message_event) { + alert(message_event.data); + } + + // If the page loads before the Native Client module loads, then set the + // status message indicating that the module is still loading. Otherwise, + // do not change the status message. + function pageDidLoad() { + // Set the focus on the text input box. Doing this means you can press + // return as soon as the page loads, and it will fire the reversetText() + // function. + document.forms.helloForm.inputBox.focus(); + if (helloWorldModule == null) { + updateStatus('LOADING...'); + } else { + // It's possible that the Native Client module onload event fired + // before the page's onload event. In this case, the status message + // will reflect 'SUCCESS', but won't be displayed. This call will + // display the current message. + updateStatus(); + } + } + + function fortyTwo() { + helloWorldModule.postMessage('fortyTwo'); + } + + function reverseText() { + // Grab the text from the text box, pass it into reverseText() + var inputBox = document.forms.helloForm.inputBox; + helloWorldModule.postMessage('reverseText:' + inputBox.value); + // Note: a |false| return tells the <form> tag to cancel the GET action + // when submitting the form. + return false; + } + + // Set the global status message. If the element with id 'statusField' + // exists, then set its HTML to the status message as well. + // opt_message The message test. If this is null or undefined, then + // attempt to set the element with id 'statusField' to the value of + // |statusText|. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + var statusField = document.getElementById('statusField'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + </script> +</head> +<body onload="pageDidLoad()"> + +<h1>Native Client Simple Module</h1> +<p> + <form name="helloForm" + action="" + method="get" + onsubmit="return reverseText()"> + <input type="text" id="inputBox" name="inputBox" value="Hello world" /><p/> + <input type="button" value="Call fortyTwo()" onclick="fortyTwo()" /> + <input type="submit" value="Call reverseText()" /> + </form> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="hello_world" + width=0 height=0 + src="hello_world.nmf" + type="application/x-nacl" /> + </div> + +</p> + +<p>If the module is working correctly, a click on the "Call fortyTwo()" button + should open a popup dialog containing <b>42</b> as its value.</p> + +<p> Clicking on the "Call reverseText()" button + should open a popup dialog containing the textbox contents and its reverse + as its value.</p> + +<h2>Status</h2> +<div id="statusField">NO-STATUS</div> +</body> +</html> diff --git a/native_client_sdk/src/examples/hello_world/helper_functions.cc b/native_client_sdk/src/examples/hello_world/helper_functions.cc new file mode 100644 index 0000000..5ed26b5 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world/helper_functions.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/hello_world/helper_functions.h" + +#include <algorithm> + +namespace hello_world { + +int32_t FortyTwo() { + return 42; +} + +std::string ReverseText(const std::string& text) { + std::string reversed_string(text); + // Use reverse to reverse |reversed_string| in place. + std::reverse(reversed_string.begin(), reversed_string.end()); + return reversed_string; +} +} // namespace hello_world + diff --git a/native_client_sdk/src/examples/hello_world/helper_functions.h b/native_client_sdk/src/examples/hello_world/helper_functions.h new file mode 100644 index 0000000..69ab874 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world/helper_functions.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_HELLO_WORLD_HELPER_FUNCTIONS_H_ +#define EXAMPLES_HELLO_WORLD_HELPER_FUNCTIONS_H_ + +/// @file +/// These functions are stand-ins for your complicated computations which you +/// want to run in native code. We do two very simple things: return 42, and +/// reverse a string. But you can imagine putting more complicated things here +/// which might be difficult or slow to achieve in JavaScript, such as +/// cryptography, artificial intelligence, signal processing, physics modeling, +/// etc. See hello_world.cc for the code which is required for loading a NaCl +/// application and exposing methods to JavaScript. + +#include <ppapi/c/pp_stdint.h> +#include <string> + +namespace hello_world { + +/// This is the module's function that does the work to compute the value 42. +int32_t FortyTwo(); + +/// This function is passed a string and returns a copy of the string with the +/// characters in reverse order. +/// @param[in] text The string to reverse. +/// @return A copy of @a text with the characters in reverse order. +std::string ReverseText(const std::string& text); + +} // namespace hello_world + +#endif // EXAMPLES_HELLO_WORLD_HELPER_FUNCTIONS_H_ + diff --git a/native_client_sdk/src/examples/hello_world/test_helper_functions.cc b/native_client_sdk/src/examples/hello_world/test_helper_functions.cc new file mode 100644 index 0000000..f73f71a --- /dev/null +++ b/native_client_sdk/src/examples/hello_world/test_helper_functions.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is an example of a simple unit test to verify that the logic helper +// functions works as expected. Note that this looks like a 'normal' C++ +// program, with a main function. It is compiled and linked using the NaCl +// toolchain, so in order to run it, you must use 'sel_ldr_x86_32' or +// 'sel_ldr_x86_64' from the toolchain's bin directory. +// +// For example (assuming the toolchain bin directory is in your path): +// sel_ldr_x86_32 test_helper_functions_x86_32_dbg.nexe +// +// You can also use the 'test32', or 'test64' SCons target to run these tests. +// For example, this will run the test in 32-bit mode on Mac or Linux: +// ../scons test32 +// On Windows 64: +// ..\scons test64 + +#include "examples/hello_world/helper_functions.h" + +#include <cassert> +#include <cstdio> +#include <string> + +// A very simple macro to print 'passed' if boolean_expression is true and +// 'FAILED' otherwise. +// This is meant to approximate the functionality you would get from a real test +// framework. You should feel free to build and use the test framework of your +// choice. +#define EXPECT_EQUAL(left, right)\ +printf("Check: \"" #left "\" == \"" #right "\" %s\n", \ + ((left) == (right)) ? "passed" : "FAILED") + +using hello_world::FortyTwo; +using hello_world::ReverseText; + +int main() { + EXPECT_EQUAL(FortyTwo(), 42); + + std::string empty_string; + EXPECT_EQUAL(ReverseText(empty_string), empty_string); + + std::string palindrome("able was i ere i saw elba"); + EXPECT_EQUAL(ReverseText(palindrome), palindrome); + + std::string alphabet("abcdefghijklmnopqrstuvwxyz"); + std::string alphabet_backwards("zyxwvutsrqponmlkjihgfedcba"); + EXPECT_EQUAL(ReverseText(alphabet), alphabet_backwards); +} + diff --git a/native_client_sdk/src/examples/hello_world_c/build.scons b/native_client_sdk/src/examples/hello_world_c/build.scons new file mode 100644 index 0000000..90666e5 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world_c/build.scons @@ -0,0 +1,40 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='hello_world_c', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['hello_world_c.c'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'hello_world_c') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('hello_world_c') + +app_files = [ + 'hello_world_c.html', + 'hello_world_c.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/hello_world_c/hello_world_c.c b/native_client_sdk/src/examples/hello_world_c/hello_world_c.c new file mode 100644 index 0000000..939e724 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world_c/hello_world_c.c @@ -0,0 +1,287 @@ +/* Copyright (c) 2011 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** @file hello_world.c + * This example demonstrates loading, running and scripting a very simple + * NaCl module. + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/pp_module.h" +#include "ppapi/c/pp_var.h" +#include "ppapi/c/ppb.h" +#include "ppapi/c/ppb_instance.h" +#include "ppapi/c/ppb_messaging.h" +#include "ppapi/c/ppb_var.h" +#include "ppapi/c/ppp.h" +#include "ppapi/c/ppp_instance.h" +#include "ppapi/c/ppp_messaging.h" + +struct MessageInfo { + PP_Instance instance; + struct PP_Var message; +}; + +static const char* const kReverseTextMethodId = "reverseText"; +static const char* const kFortyTwoMethodId = "fortyTwo"; +static const char kMessageArgumentSeparator = ':'; +static const char kNullTerminator = '\0'; + +static struct PPB_Messaging* ppb_messaging_interface = NULL; +static struct PPB_Var* ppb_var_interface = NULL; +static PP_Module module_id = 0; + + +/** + * Returns a mutable C string contained in the @a var or NULL if @a var is not + * string. This makes a copy of the string in the @ var and adds a NULL + * terminator. Note that VarToUtf8() does not guarantee the NULL terminator on + * the returned string. See the comments for VatToUtf8() in ppapi/c/ppb_var.h + * for more info. The caller is responsible for freeing the returned memory. + * @param[in] var PP_Var containing string. + * @return a C string representation of @a var. + * @note The caller is responsible for freeing the returned string. + */ +static char* VarToCStr(struct PP_Var var) { + uint32_t len = 0; + if (ppb_var_interface != NULL) { + const char* var_c_str = ppb_var_interface->VarToUtf8(var, &len); + if (len > 0) { + char* c_str = (char*)malloc(len + 1); + memcpy(c_str, var_c_str, len); + c_str[len] = kNullTerminator; + return c_str; + } + } + return NULL; +} + +/** + * Creates new string PP_Var from C string. The resulting object will be a + * refcounted string object. It will be AddRef()ed for the caller. When the + * caller is done with it, it should be Release()d. + * @param[in] str C string to be converted to PP_Var + * @return PP_Var containing string. + */ +static struct PP_Var CStrToVar(const char* str) { + if (ppb_var_interface != NULL) { + return ppb_var_interface->VarFromUtf8(module_id, str, strlen(str)); + } + return PP_MakeUndefined(); +} + +/** + * Reverse C string in-place. + * @param[in,out] str C string to be reversed + */ +static void ReverseStr(char* str) { + char* right = str + strlen(str) - 1; + char* left = str; + while (left < right) { + char tmp = *left; + *left++ = *right; + *right-- = tmp; + } +} + +/** + * A simple function that always returns 42. + * @return always returns the integer 42 + */ +static struct PP_Var FortyTwo() { + return PP_MakeInt32(42); +} + +/** + * Called when the NaCl module is instantiated on the web page. The identifier + * of the new instance will be passed in as the first argument (this value is + * generated by the browser and is an opaque handle). This is called for each + * instantiation of the NaCl module, which is each time the <embed> tag for + * this module is encountered. + * + * If this function reports a failure (by returning @a PP_FALSE), the NaCl + * module will be deleted and DidDestroy will be called. + * @param[in] instance The identifier of the new instance representing this + * NaCl module. + * @param[in] argc The number of arguments contained in @a argn and @a argv. + * @param[in] argn An array of argument names. These argument names are + * supplied in the <embed> tag, for example: + * <embed id="nacl_module" dimensions="2"> + * will produce two arguments, one named "id" and one named "dimensions". + * @param[in] argv An array of argument values. These are the values of the + * arguments listed in the <embed> tag. In the above example, there will + * be two elements in this array, "nacl_module" and "2". The indices of + * these values match the indices of the corresponding names in @a argn. + * @return @a PP_TRUE on success. + */ +static PP_Bool Instance_DidCreate(PP_Instance instance, + uint32_t argc, + const char* argn[], + const char* argv[]) { + return PP_TRUE; +} + +/** + * Called when the NaCl module is destroyed. This will always be called, + * even if DidCreate returned failure. This routine should deallocate any data + * associated with the instance. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + */ +static void Instance_DidDestroy(PP_Instance instance) { +} + +/** + * Called when the position, the size, or the clip rect of the element in the + * browser that corresponds to this NaCl module has changed. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] position The location on the page of this NaCl module. This is + * relative to the top left corner of the viewport, which changes as the + * page is scrolled. + * @param[in] clip The visible region of the NaCl module. This is relative to + * the top left of the plugin's coordinate system (not the page). If the + * plugin is invisible, @a clip will be (0, 0, 0, 0). + */ +static void Instance_DidChangeView(PP_Instance instance, + const struct PP_Rect* position, + const struct PP_Rect* clip) { +} + +/** + * Notification that the given NaCl module has gained or lost focus. + * Having focus means that keyboard events will be sent to the NaCl module + * represented by @a instance. A NaCl module's default condition is that it + * will not have focus. + * + * Note: clicks on NaCl modules will give focus only if you handle the + * click event. You signal if you handled it by returning @a true from + * HandleInputEvent. Otherwise the browser will bubble the event and give + * focus to the element on the page that actually did end up consuming it. + * If you're not getting focus, check to make sure you're returning true from + * the mouse click in HandleInputEvent. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] has_focus Indicates whether this NaCl module gained or lost + * event focus. + */ +static void Instance_DidChangeFocus(PP_Instance instance, + PP_Bool has_focus) { +} + +/** + * Handler that gets called after a full-frame module is instantiated based on + * registered MIME types. This function is not called on NaCl modules. This + * function is essentially a place-holder for the required function pointer in + * the PPP_Instance structure. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] url_loader A PP_Resource an open PPB_URLLoader instance. + * @return PP_FALSE. + */ +static PP_Bool Instance_HandleDocumentLoad(PP_Instance instance, + PP_Resource url_loader) { + /* NaCl modules do not need to handle the document load function. */ + return PP_FALSE; +} + +/** + * Handler for messages coming in from the browser via postMessage. Extracts + * the method call from @a message, parses it for method name and value, then + * calls the appropriate function. In the case of the reverseString method, the + * message format is a simple colon-separated string. The first part of the + * string up to the colon is the method name; after that is the string argument. + * @param[in] instance The instance ID. + * @param[in] message The contents, copied by value, of the message sent from + * browser via postMessage. + */ +void Messaging_HandleMessage(PP_Instance instance, struct PP_Var var_message) { + if (var_message.type != PP_VARTYPE_STRING) { + /* Only handle string messages */ + return; + } + char* message = VarToCStr(var_message); + if (message == NULL) + return; + struct PP_Var var_result = PP_MakeUndefined(); + if (strncmp(message, kFortyTwoMethodId, strlen(kFortyTwoMethodId)) == 0) { + var_result = FortyTwo(); + } else if (strncmp(message, + kReverseTextMethodId, + strlen(kReverseTextMethodId)) == 0) { + /* Use everything after the ':' in |message| as the string argument. */ + char* string_arg = strchr(message, kMessageArgumentSeparator); + if (string_arg != NULL) { + string_arg += 1; /* Advance past the ':' separator. */ + ReverseStr(string_arg); + var_result = CStrToVar(string_arg); + } + } + free(message); + + /* Echo the return result back to browser. Note that HandleMessage is always + * called on the main thread, so it's OK to post the message back to the + * browser directly from here. This return post is asynchronous. + */ + ppb_messaging_interface->PostMessage(instance, var_result); + /* If the message was created using VarFromUtf8() it needs to be released. + * See the comments about VarFromUtf8() in ppapi/c/ppb_var.h for more + * information. + */ + if (var_result.type == PP_VARTYPE_STRING) { + ppb_var_interface->Release(var_result); + } +} + +/** + * Entry points for the module. + * Initialize needed interfaces: PPB_Core, PPB_Messaging and PPB_Var. + * @param[in] a_module_id module ID + * @param[in] get_browser pointer to PPB_GetInterface + * @return PP_OK on success, any other value on failure. + */ +PP_EXPORT int32_t PPP_InitializeModule(PP_Module a_module_id, + PPB_GetInterface get_browser) { + module_id = a_module_id; + ppb_messaging_interface = + (struct PPB_Messaging*)(get_browser(PPB_MESSAGING_INTERFACE)); + ppb_var_interface = (struct PPB_Var*)(get_browser(PPB_VAR_INTERFACE)); + + return PP_OK; +} + +/** + * Returns an interface pointer for the interface of the given name, or NULL + * if the interface is not supported. + * @param[in] interface_name name of the interface + * @return pointer to the interface + */ +PP_EXPORT const void* PPP_GetInterface(const char* interface_name) { + if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0) { + static struct PPP_Instance instance_interface = { + &Instance_DidCreate, + &Instance_DidDestroy, + &Instance_DidChangeView, + &Instance_DidChangeFocus, + &Instance_HandleDocumentLoad, + }; + return &instance_interface; + } else if (strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0) { + static struct PPP_Messaging messaging_interface = { + &Messaging_HandleMessage + }; + return &messaging_interface; + } + return NULL; +} + +/** + * Called before the plugin module is unloaded. + */ +PP_EXPORT void PPP_ShutdownModule() { +} diff --git a/native_client_sdk/src/examples/hello_world_c/hello_world_c.html b/native_client_sdk/src/examples/hello_world_c/hello_world_c.html new file mode 100644 index 0000000..e5511c6 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world_c/hello_world_c.html @@ -0,0 +1,126 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> +<head> + <title>Hello, World!</title> + + <script type="text/javascript"> + helloWorldModule = null; // Global application object. + statusText = 'NO-STATUS'; + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + helloWorldModule = document.getElementById('hello_world'); + updateStatus('SUCCESS'); + } + + // Handle a message coming from the NaCl module. + function handleMessage(message_event) { + alert(message_event.data); + } + + // If the page loads before the Native Client module loads, then set the + // status message indicating that the module is still loading. Otherwise, + // do not change the status message. + function pageDidLoad() { + // Set the focus on the text input box. Doing this means you can press + // return as soon as the page loads, and it will fire the reversetText() + // function. + document.forms.helloForm.inputBox.focus(); + if (helloWorldModule == null) { + updateStatus('LOADING...'); + } else { + // It's possible that the Native Client module onload event fired + // before the page's onload event. In this case, the status message + // will reflect 'SUCCESS', but won't be displayed. This call will + // display the current message. + updateStatus(); + } + } + + function fortyTwo() { + helloWorldModule.postMessage('fortyTwo'); + } + + function reverseText() { + // Grab the text from the text box, pass it into reverseText() + var inputBox = document.forms.helloForm.inputBox; + helloWorldModule.postMessage('reverseText:' + inputBox.value); + // Note: a |false| return tells the <form> tag to cancel the GET action + // when submitting the form. + return false; + } + + // Set the global status message. If the element with id 'statusField' + // exists, then set its HTML to the status message as well. + // opt_message The message test. If this is null or undefined, then + // attempt to set the element with id 'statusField' to the value of + // |statusText|. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + var statusField = document.getElementById('statusField'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + </script> +</head> +<body onload="pageDidLoad()"> + +<h1>Native Client Simple Module</h1> +<p> + <form name="helloForm" + action="" + method="get" + onsubmit="return reverseText()"> + <input type="text" id="inputBox" name="inputBox" value="Hello world" /><p/> + <input type="button" value="Call fortyTwo()" onclick="fortyTwo()" /> + <input type="submit" value="Call reverseText()" /> + </form> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="hello_world" + width=0 height=0 + src="hello_world_c.nmf" + type="application/x-nacl" /> + </div> + +</p> + +<p>If the module is working correctly, a click on the "Call fortyTwo()" button + should open a popup dialog containing <b>42</b> as its value.</p> + +<p> Clicking on the "Call reverseText()" button + should open a popup dialog containing the textbox contents and its reverse + as its value.</p> + +<h2>Status</h2> +<div id="statusField">NO-STATUS</div> +</body> +</html> diff --git a/native_client_sdk/src/examples/hello_world_c/hello_world_c_dbg.html b/native_client_sdk/src/examples/hello_world_c/hello_world_c_dbg.html new file mode 100644 index 0000000..c93fb79 --- /dev/null +++ b/native_client_sdk/src/examples/hello_world_c/hello_world_c_dbg.html @@ -0,0 +1,126 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> +<head> + <title>Hello, World!</title> + + <script type="text/javascript"> + helloWorldModule = null; // Global application object. + statusText = 'NO-STATUS'; + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + helloWorldModule = document.getElementById('hello_world'); + updateStatus('SUCCESS'); + } + + // Handle a message coming from the NaCl module. + function handleMessage(message_event) { + alert(message_event.data); + } + + // If the page loads before the Native Client module loads, then set the + // status message indicating that the module is still loading. Otherwise, + // do not change the status message. + function pageDidLoad() { + // Set the focus on the text input box. Doing this means you can press + // return as soon as the page loads, and it will fire the reversetText() + // function. + document.forms.helloForm.inputBox.focus(); + if (helloWorldModule == null) { + updateStatus('LOADING...'); + } else { + // It's possible that the Native Client module onload event fired + // before the page's onload event. In this case, the status message + // will reflect 'SUCCESS', but won't be displayed. This call will + // display the current message. + updateStatus(); + } + } + + function fortyTwo() { + helloWorldModule.postMessage('fortyTwo'); + } + + function reverseText() { + // Grab the text from the text box, pass it into reverseText() + var inputBox = document.forms.helloForm.inputBox; + helloWorldModule.postMessage('reverseText:' + inputBox.value); + // Note: a |false| return tells the <form> tag to cancel the GET action + // when submitting the form. + return false; + } + + // Set the global status message. If the element with id 'statusField' + // exists, then set its HTML to the status message as well. + // opt_message The message test. If this is null or undefined, then + // attempt to set the element with id 'statusField' to the value of + // |statusText|. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + var statusField = document.getElementById('statusField'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + </script> +</head> +<body onload="pageDidLoad()"> + +<h1>Native Client Simple Module</h1> +<p> + <form name="helloForm" + action="" + method="get" + onsubmit="return reverseText()"> + <input type="text" id="inputBox" name="inputBox" value="Hello world" /><p> + <input type="button" value="Call fortyTwo()" onclick="fortyTwo()" /> + <input type="submit" value="Call reverseText()" /> + </form> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="hello_world" + width=0 height=0 + src="hello_world_c_dbg.nmf" + type="application/x-nacl" /> + </div> + +</p> + +<p>If the module is working correctly, a click on the "Call fortyTwo()" button + should open a popup dialog containing <b>42</b> as its value.</p> + +<p> Clicking on the "Call reverseText()" button + should open a popup dialog containing the textbox contents and its reverse + as its value.</p> + +<h2>Status</h2> +<div id="statusField">NO-STATUS</div> +</body> +</html> diff --git a/native_client_sdk/src/examples/httpd.cmd b/native_client_sdk/src/examples/httpd.cmd new file mode 100644 index 0000000..7c64624 --- /dev/null +++ b/native_client_sdk/src/examples/httpd.cmd @@ -0,0 +1,8 @@ +@echo off
+setlocal
+
+PATH=%CYGWIN%;%PATH%
+REM Use the path to this file (httpd.cmd) to get the
+REM path to httpd.py, so that we can run httpd.cmd from
+REM any directory. Pass up to 9 arguments to httpd.py.
+python %~dp0\httpd.py %1 %2 %3 %4 %5 %6 %7 %8 %9
diff --git a/native_client_sdk/src/examples/httpd.py b/native_client_sdk/src/examples/httpd.py new file mode 100755 index 0000000..3fa8b22 --- /dev/null +++ b/native_client_sdk/src/examples/httpd.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# + +"""A tiny web server. + +This is intended to be used for testing, and only run from within the examples +directory. +""" + +import BaseHTTPServer +import logging +import optparse +import os +import SimpleHTTPServer +import SocketServer +import sys +import urlparse + +logging.getLogger().setLevel(logging.INFO) + +# Using 'localhost' means that we only accept connections +# via the loop back interface. +SERVER_PORT = 5103 +SERVER_HOST = '' + +# We only run from the examples or staging directory so +# that not too much is exposed via this HTTP server. Everything in the +# directory is served, so there should never be anything potentially sensitive +# in the serving directory, especially if the machine might be a +# multi-user machine and not all users are trusted. We only serve via +# the loopback interface. + +SAFE_DIR_COMPONENTS = ['staging', 'examples'] + +def SanityCheckDirectory(): + if os.path.basename(os.getcwd()) in SAFE_DIR_COMPONENTS: + return + logging.error('For security, httpd.py should only be run from one of the') + logging.error('following directories: %s' % SAFE_DIR_COMPONENTS) + logging.error('We are currently in %s', os.getcwd()) + sys.exit(1) + + +# An HTTP server that will quit when |is_running| is set to False. We also use +# SocketServer.ThreadingMixIn in order to handle requests asynchronously for +# faster responses. +class QuittableHTTPServer(SocketServer.ThreadingMixIn, + BaseHTTPServer.HTTPServer): + def serve_forever(self, timeout=0.5): + self.is_running = True + self.timeout = timeout + while self.is_running: + self.handle_request() + + def shutdown(self): + self.is_running = False + return 1 + + +# "Safely" split a string at |sep| into a [key, value] pair. If |sep| does not +# exist in |str|, then the entire |str| is the key and the value is set to an +# empty string. +def KeyValuePair(str, sep='='): + if sep in str: + return str.split(sep) + else: + return [str, ''] + + +# A small handler that looks for '?quit=1' query in the path and shuts itself +# down if it finds that parameter. +class QuittableHTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def do_GET(self): + (_, _, _, query, _) = urlparse.urlsplit(self.path) + url_params = dict([KeyValuePair(key_value) + for key_value in query.split('&')]) + if 'quit' in url_params and '1' in url_params['quit']: + self.send_response(200, 'OK') + self.send_header('Content-type', 'text/html') + self.send_header('Content-length', '0') + self.end_headers() + self.server.shutdown() + return + + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + + +def Run(server_address, + server_class=QuittableHTTPServer, + handler_class=QuittableHTTPHandler): + httpd = server_class(server_address, handler_class) + logging.info("Starting local server on port %d", server_address[1]) + logging.info("To shut down send http://localhost:%d?quit=1", + server_address[1]) + try: + httpd.serve_forever() + except KeyboardInterrupt: + logging.info("Received keyboard interrupt.") + httpd.server_close() + + logging.info("Shutting down local server on port %d", server_address[1]) + + +if __name__ == '__main__': + usage_str = "usage: %prog [options] [optional_portnum]" + parser = optparse.OptionParser(usage=usage_str) + parser.add_option( + '--no_dir_check', dest='do_safe_check', + action='store_false', default=True, + help='Do not ensure that httpd.py is being run from a safe directory.') + (options, args) = parser.parse_args(sys.argv) + if options.do_safe_check: + SanityCheckDirectory() + if len(args) > 2: + print 'Too many arguments specified.' + parser.print_help() + elif len(args) == 2: + Run((SERVER_HOST, int(args[1]))) + else: + Run((SERVER_HOST, SERVER_PORT)) + sys.exit(0) diff --git a/native_client_sdk/src/examples/index.html b/native_client_sdk/src/examples/index.html new file mode 100644 index 0000000..3d72c28 --- /dev/null +++ b/native_client_sdk/src/examples/index.html @@ -0,0 +1,51 @@ +<!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<!DOCTYPE html PUBLIC + "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<style type="text/css"> +dt { + font-weight: bold; +} +dd { + margin-bottom: 12pt; + width: 600px; +} +</style> +<link href="http://code.google.com/css/codesite.css" rel="stylesheet" + type="text/css" /> +<title>Native Client Examples</title> +</head> +<body> +<h2>Native Client Examples</h2> +<p>The examples are no longer pre-built in the SDK. To try out the Native +Client examples right now in your Chrome web browser, please see the +<a href="http://www.gonacl.com/dev/sdk.html">SDK page on GoNaCl.com</a> and +download the SDK examples from the +<a href="https://chrome.google.com/webstore/">Chrome Web Store</a>.</p> +<p>If you would like to build and run the examples within the SDK +then run these commands, starting from the examples directory:</p><br /> +<strong>Windows</strong> +<blockquote><code> +cd %NACL_SDK_ROOT%\%NACL_TARGET_PLATFORM%\examples<br /> +scons<br /> +cd %NACL_SDK_ROOT%\staging<br /> +httpd<br /> +</code></blockquote> +<strong>Mac/Linux</strong> +<blockquote><code> +cd $NACL_SDK_ROOT/$NACL_TARGET_PLATFORM/examples<br /> +./scons<br /> +cd $NACL_SDK_ROOT/staging<br /> +./httpd.py<br /> +</code></blockquote> +<p>Happy hacking!</p> +</body> +</html> diff --git a/native_client_sdk/src/examples/index_staging.html b/native_client_sdk/src/examples/index_staging.html new file mode 100644 index 0000000..c90ec1a --- /dev/null +++ b/native_client_sdk/src/examples/index_staging.html @@ -0,0 +1,113 @@ +<!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<!DOCTYPE html PUBLIC + "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<style type="text/css"> +dt { + font-weight: bold; +} +dd { + margin-bottom: 12pt; + width: 600px; +} +</style> +<link href="http://code.google.com/css/codesite.css" rel="stylesheet" + type="text/css" /> +<title>Native Client Examples</title> +</head> +<body> +<h2>Native Client Examples</h2> +<p>This page lists all of the examples available in the most recent Native Client SDK bundle. Each example is designed to teach a few specific Native Client programming concepts.</p> +<dl> + <dt><a href="hello_world_c/hello_world_c.html">Hello World in C</a></dt> + <dd>The Hello World In C example demonstrates the basic structure of all Native Client applications. This example loads a Native Client module and responds to button click events by showing alert panels. + + <p>Teaching focus: Basic HTML, JavaScript, and module architecture; Messaging API.</p> + </dd> + <dt><a href="hello_world/hello_world.html">Hello World in C++</a></dt> + <dd>The Hello World C++ example demonstrates the basic structure of all Native Client applications. This example loads a Native Client module and responds to button click events by showing alert panels. + + <p>Teaching focus: Basic HTML, JavaScript, and module architecture; Messaging API.</p> + </dd> +<dt><a href="load_progress/load_progress.html">Load Progress</a></dt> + <dd> The Load Progress example demonstrates how to listen for and handle events that occur while a + NaCl module loads. This example listens for different load event types and dispatches different events to their respective handler. This example also checks for valid browser + version and shows how to calculate and display loading progress. + + <p>Teaching focus: Progress event handling.</p> + </dd> + <dt><a href="pi_generator/pi_generator.html">Pi Generator</a></dt> + <dd> The Pi Generator example demonstrates creating a helper thread that estimate pi using the Monte Carlo + method while randomly putting 1,000,000,000 points inside a 2D square that shares two + sides with a quarter circle. + + <p>Teaching focus: Thread creation, 2D graphics, view change events.</p> + </dd> +<dt><a href="input_events/input_events.html">Input Events</a></dt> + <dd> The Input Events example demonstrates how to handle events triggered by the user. This example allows a user + to interact with a square representing a module instance. Events are displayed on the screen as the user clicks, scrolls, types, inside or outside + of the square. + + <p>Teaching focus: Keyboard and mouse input, view change, and focus events.</p> + </dd> +<dt><a href="sine_synth/sine_synth.html">Sine Wave Synthesizer</a></dt> + <dd> The Sine Wave Synthesizer example demonstrates playing sound (a sine wave). + + <p>Teaching focus: Audio.</p> + </dd> +<dt><a href="pong/pong.html">Pong</a></dt> + <dd> The Pong example demonstrates how to create a basic 2D video game and how to store application + information in a local persistent file. This game uses up and + down arrow keyboard input events to move the paddle. + + <p>Teaching focus: File I/O, 2D graphics, input events.</p> + </dd> + <dt><a href="geturl/geturl.html">Get URL</a></dt> + <dd> The Get URL example demonstrates fetching an URL and then displaying its contents. + + <p>Teaching focus: URL loading.</p> + </dd> + <dt><a href="multithreaded_input_events/mt_input_events.html">Multi-threaded Input Events</a></dt> + <dd>The Multithreaded Input Events example combines HTML, Javascript, + and C++ (the C++ is compiled to create a .nexe file). + The C++ shows how to handle input events in a multi-threaded application. + The main thread converts input events to non-pepper events and puts them on + a queue. The worker thread pulls them off of the queue, converts them to a + string, and then uses CallOnMainThread so that PostMessage can be send the + result of the worker thread to the browser. + </dd> + <dt><a href="tumbler/tumbler.html">Tumbler</a></dt> + <dd> The Tumbler example demonstrates how to create a 3D cube that you can rotate with your mouse while pressing the + left mouse button. This example creates a 3D context and draws to it using + OpenGL ES. The JavaScript implements a virtual trackball interface to + map mouse movements into 3D rotations using simple 3D vector math and + quaternions. + + <p>Teaching focus: 3D graphics</p> + </dd> + <dt><a href="fullscreen_tumbler/fullscreen_tumbler.html">Full-screen Tumbler</a></dt> + <dd> This is a modified version of the Tumbler example above that supports + full-screen display. It is in every way identical to Tumbler in + functionality, except that it adds the ability to switch to/from + full-screen display by pressing the Enter key. + + <p>Teaching focus: Full-screen</p> + </dd> + <dt><a href="mouselock/mouselock.html">Mouse Locker</a></dt> + <dd> The Mouselock example demonstrates how to use the MouseLock API to hide + the mouse cursor. Mouse lock is only available in full-screen mode. You can + lock and unlock the mouse while in full-screen mode by pressing the Enter key. + + <p>Teaching focus: Mouse lock, Full-screen</p> + </dd> +</dl> +</body> +</html> diff --git a/native_client_sdk/src/examples/input_events/build.scons b/native_client_sdk/src/examples/input_events/build.scons new file mode 100644 index 0000000..3ccfc5b --- /dev/null +++ b/native_client_sdk/src/examples/input_events/build.scons @@ -0,0 +1,41 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import nacl_utils +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='input_events', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['input_events.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'input_events') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('input_events') + +app_files = [ + 'input_events.html', + 'input_events.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/input_events/input_events.cc b/native_client_sdk/src/examples/input_events/input_events.cc new file mode 100644 index 0000000..8ef2419 --- /dev/null +++ b/native_client_sdk/src/examples/input_events/input_events.cc @@ -0,0 +1,251 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// C headers +#include <cassert> +#include <cstdio> + +// C++ headers +#include <sstream> +#include <string> + +// NaCl +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/point.h" +#include "ppapi/cpp/var.h" + +namespace { +const char* const kDidChangeView = "DidChangeView"; +const char* const kHandleInputEvent = "DidHandleInputEvent"; +const char* const kDidChangeFocus = "DidChangeFocus"; +const char* const kHaveFocus = "HaveFocus"; +const char* const kDontHaveFocus = "DontHaveFocus"; + +// Convert a given modifier to a descriptive string. Note that the actual +// declared type of modifier in each of the event classes is uint32_t, but it is +// expected to be interpreted as a bitfield of 'or'ed PP_InputEvent_Modifier +// values. +std::string ModifierToString(uint32_t modifier) { + std::string s; + if (modifier & PP_INPUTEVENT_MODIFIER_SHIFTKEY) { + s += "shift "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_CONTROLKEY) { + s += "ctrl "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_ALTKEY) { + s += "alt "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_METAKEY) { + s += "meta "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_ISKEYPAD) { + s += "keypad "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_ISAUTOREPEAT) { + s += "autorepeat "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) { + s += "left-button-down "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN) { + s += "middle-button-down "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_RIGHTBUTTONDOWN) { + s += "right-button-down "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY) { + s += "caps-lock "; + } + if (modifier & PP_INPUTEVENT_MODIFIER_NUMLOCKKEY) { + s += "num-lock "; + } + return s; +} + +std::string MouseButtonToString(PP_InputEvent_MouseButton button) { + switch (button) { + case PP_INPUTEVENT_MOUSEBUTTON_NONE: + return "None"; + case PP_INPUTEVENT_MOUSEBUTTON_LEFT: + return "Left"; + case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE: + return "Middle"; + case PP_INPUTEVENT_MOUSEBUTTON_RIGHT: + return "Right"; + default: + std::ostringstream stream; + stream << "Unrecognized (" + << static_cast<int32_t>(button) + << ")"; + return stream.str(); + } +} + +} // namespace + +class EventInstance : public pp::Instance { + public: + explicit EventInstance(PP_Instance instance) + : pp::Instance(instance) { + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL); + RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); + } + virtual ~EventInstance() {} + + /// Clicking outside of the instance's bounding box + /// will create a DidChangeFocus event (the NaCl instance is + /// out of focus). Clicking back inside the instance's + /// bounding box will create another DidChangeFocus event + /// (the NaCl instance is back in focus). The default is + /// that the instance is out of focus. + void DidChangeFocus(bool focus) { + PostMessage(pp::Var(kDidChangeFocus)); + if (focus == true) { + PostMessage(pp::Var(kHaveFocus)); + } else { + PostMessage(pp::Var(kDontHaveFocus)); + } + } + + /// Scrolling the mouse wheel causes a DidChangeView event. + void DidChangeView(const pp::Rect& position, + const pp::Rect& clip) { + PostMessage(pp::Var(kDidChangeView)); + } + + void GotKeyEvent(const pp::KeyboardInputEvent& key_event, + const std::string& kind) { + std::ostringstream stream; + stream << pp_instance() << ":" + << " Key event:" << kind + << " modifier:" << ModifierToString(key_event.GetModifiers()) + << " key_code:" << key_event.GetKeyCode() + << " time:" << key_event.GetTimeStamp() + << " text:" << key_event.GetCharacterText().DebugString() + << "\n"; + PostMessage(stream.str()); + } + + void GotMouseEvent(const pp::MouseInputEvent& mouse_event, + const std::string& kind) { + std::ostringstream stream; + stream << pp_instance() << ":" + << " Mouse event:" << kind + << " modifier:" << ModifierToString(mouse_event.GetModifiers()) + << " button:" << MouseButtonToString(mouse_event.GetButton()) + << " x:" << mouse_event.GetPosition().x() + << " y:" << mouse_event.GetPosition().y() + << " click_count:" << mouse_event.GetClickCount() + << " time:" << mouse_event.GetTimeStamp() + << "\n"; + PostMessage(stream.str()); + } + + void GotWheelEvent(const pp::WheelInputEvent& wheel_event) { + std::ostringstream stream; + stream << pp_instance() << ": Wheel event." + << " modifier:" << ModifierToString(wheel_event.GetModifiers()) + << " deltax:" << wheel_event.GetDelta().x() + << " deltay:" << wheel_event.GetDelta().y() + << " wheel_ticks_x:" << wheel_event.GetTicks().x() + << " wheel_ticks_y:"<< wheel_event.GetTicks().y() + << " scroll_by_page: " + << (wheel_event.GetScrollByPage() ? "true" : "false") + << "\n"; + PostMessage(stream.str()); + } + + // Handle an incoming input event by switching on type and dispatching + // to the appropriate subtype handler. + // + // HandleInputEvent operates on the main Pepper thread. In large + // real-world applications, you'll want to create a separate thread + // that puts events in a queue and handles them independant of the main + // thread so as not to slow down the browser. There is an additional + // version of this example in the examples directory that demonstrates + // this best practice. + virtual bool HandleInputEvent(const pp::InputEvent& event) { + PostMessage(pp::Var(kHandleInputEvent)); + switch (event.GetType()) { + case PP_INPUTEVENT_TYPE_UNDEFINED: + break; + case PP_INPUTEVENT_TYPE_MOUSEDOWN: + GotMouseEvent(pp::MouseInputEvent(event), "Down"); + break; + case PP_INPUTEVENT_TYPE_MOUSEUP: + GotMouseEvent(pp::MouseInputEvent(event), "Up"); + break; + case PP_INPUTEVENT_TYPE_MOUSEMOVE: + GotMouseEvent(pp::MouseInputEvent(event), "Move"); + break; + case PP_INPUTEVENT_TYPE_MOUSEENTER: + GotMouseEvent(pp::MouseInputEvent(event), "Enter"); + break; + case PP_INPUTEVENT_TYPE_MOUSELEAVE: + GotMouseEvent(pp::MouseInputEvent(event), "Leave"); + break; + case PP_INPUTEVENT_TYPE_WHEEL: + GotWheelEvent(pp::WheelInputEvent(event)); + break; + case PP_INPUTEVENT_TYPE_RAWKEYDOWN: + GotKeyEvent(pp::KeyboardInputEvent(event), "RawKeyDown"); + break; + case PP_INPUTEVENT_TYPE_KEYDOWN: + GotKeyEvent(pp::KeyboardInputEvent(event), "Down"); + break; + case PP_INPUTEVENT_TYPE_KEYUP: + GotKeyEvent(pp::KeyboardInputEvent(event), "Up"); + break; + case PP_INPUTEVENT_TYPE_CHAR: + GotKeyEvent(pp::KeyboardInputEvent(event), "Character"); + break; + case PP_INPUTEVENT_TYPE_CONTEXTMENU: + GotKeyEvent(pp::KeyboardInputEvent(event), "Context"); + break; + // Note that if we receive an IME event we just send a message back + // to the browser to indicate we have received it. + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: + PostMessage(pp::Var("PP_INPUTEVENT_TYPE_IME_COMPOSITION_START")); + break; + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: + PostMessage(pp::Var("PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE")); + break; + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: + PostMessage(pp::Var("PP_INPUTEVENT_TYPE_IME_COMPOSITION_END")); + break; + case PP_INPUTEVENT_TYPE_IME_TEXT: + PostMessage(pp::Var("PP_INPUTEVENT_TYPE_IME_COMPOSITION_TEXT")); + break; + default: + assert(false); + return false; + } + return true; + } +}; + +// The EventModule provides an implementation of pp::Module that creates +// EventInstance objects when invoked. This is part of the glue code that makes +// our example accessible to ppapi. +class EventModule : public pp::Module { + public: + EventModule() : pp::Module() {} + virtual ~EventModule() {} + + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new EventInstance(instance); + } +}; + +// Implement the required pp::CreateModule function that creates our specific +// kind of Module (in this case, EventModule). This is part of the glue code +// that makes our example accessible to ppapi. +namespace pp { + Module* CreateModule() { + return new EventModule(); + } +} diff --git a/native_client_sdk/src/examples/input_events/input_events.html b/native_client_sdk/src/examples/input_events/input_events.html new file mode 100644 index 0000000..3f1695d --- /dev/null +++ b/native_client_sdk/src/examples/input_events/input_events.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2011 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> +<head> + <title>Input Events</title> + + <script type="text/javascript"> + var kMaxArraySize = 20; + var messageArray = new Array(); + + function $(id) { + return document.getElementById(id); + } + + function receiveMessage(message) { + // Show last |kMaxArraySize| events in html. + messageArray.push(message.data); + if (messageArray.length > kMaxArraySize) { + messageArray.shift(); + } + var newData = messageArray.join('<BR>'); + document.getElementById('eventString').innerHTML = newData; + // Print event to console. + console.log(message.data); + } + </script> +</head> +<body> +<h1>InputEvent Handling Example</h1> + <div id="listener"> + <script type="text/javascript"> + $('listener').addEventListener('message', receiveMessage, true); + </script> + + <embed name="nacl_module" + id="event_module" + width=400 height=400 + src="input_events.nmf" + type="application/x-nacl" + style="background-color:gray" /> + </div> +<p> +This example demonstrates handling of input events in PPAPI.</p> +<p> +Each time an input event happens in the context of the gray box, +the embedded NaCl module posts a message describing the event +back to JavaScript, which prints a message to the JavaScript +console in Chrome and to a string on the page.</p> +<h2>Events</h2> +<pre> +<p><b id='eventString'>None</b></p> +</pre> +</body> +</html> diff --git a/native_client_sdk/src/examples/load_progress/build.scons b/native_client_sdk/src/examples/load_progress/build.scons new file mode 100644 index 0000000..1c8a6a6 --- /dev/null +++ b/native_client_sdk/src/examples/load_progress/build.scons @@ -0,0 +1,51 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='load_progress', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + CCFLAGS=['-pedantic', '-Werror'], + ) + +sources = ['load_progress.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'load_progress') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('load_progress') + +common_files = [ + 'check_browser.js', + ] +common_files = [ + os.path.join(os.path.dirname(os.getcwd()), 'common', common_file) + for common_file in common_files] + +app_files = [ + 'load_progress.html', + 'load_progress.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +common_dir = os.path.join(os.path.dirname(nacl_env['NACL_INSTALL_ROOT']), + 'common') +install_common = nacl_env.Install(dir=common_dir, source=common_files) +nacl_env.Alias('install', + source=[install_app, install_common, install_nexes]) diff --git a/native_client_sdk/src/examples/load_progress/load_progress.cc b/native_client_sdk/src/examples/load_progress/load_progress.cc new file mode 100644 index 0000000..07ce440 --- /dev/null +++ b/native_client_sdk/src/examples/load_progress/load_progress.cc @@ -0,0 +1,69 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// @file +/// This example demonstrates loading and running a very simple NaCl +/// module. To load the NaCl module, the browser first looks for the +/// CreateModule() factory method (at the end of this file). It calls +/// CreateModule() once to load the module code from your .nexe. After the +/// .nexe code is loaded, CreateModule() is not called again. +/// +/// Once the .nexe code is loaded, the browser then calls the +/// LoadProgressModule::CreateInstance() +/// method on the object returned by CreateModule(). It calls CreateInstance() +/// each time it encounters an <embed> tag that references your NaCl module. + +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" + +namespace load_progress { +/// The Instance class. One of these exists for each instance of your NaCl +/// module on the web page. The browser will ask the Module object to create +/// a new Instance for each occurrence of the <embed> tag that has these +/// attributes: +/// <pre> +/// type="application/x-nacl" +/// nacl="hello_world.nmf" +/// </pre> +class LoadProgressInstance : public pp::Instance { + public: + explicit LoadProgressInstance(PP_Instance instance) + : pp::Instance(instance) {} + virtual ~LoadProgressInstance() {} +}; + +/// The Module class. The browser calls the CreateInstance() method to create +/// an instance of your NaCl module on the web page. The browser creates a new +/// instance for each <embed> tag with +/// <code>type="application/x-nacl"</code>. +class LoadProgressModule : public pp::Module { + public: + LoadProgressModule() : pp::Module() {} + virtual ~LoadProgressModule() {} + + /// Create and return a HelloWorldInstance object. + /// @param[in] instance a handle to a plug-in instance. + /// @return a newly created HelloWorldInstance. + /// @note The browser is responsible for calling @a delete when done. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new LoadProgressInstance(instance); + } +}; +} // namespace load_progress + + +namespace pp { +/// Factory function called by the browser when the module is first loaded. +/// The browser keeps a singleton of this module. It calls the +/// CreateInstance() method on the object you return to make instances. There +/// is one instance per <embed> tag on the page. This is the main binding +/// point for your NaCl module with the browser. +/// @return new LoadProgressModule. +/// @note The browser is responsible for deleting returned @a Module. +Module* CreateModule() { + return new load_progress::LoadProgressModule(); +} +} // namespace pp + diff --git a/native_client_sdk/src/examples/load_progress/load_progress.html b/native_client_sdk/src/examples/load_progress/load_progress.html new file mode 100644 index 0000000..4c2dc47 --- /dev/null +++ b/native_client_sdk/src/examples/load_progress/load_progress.html @@ -0,0 +1,232 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> +<head> + <title>Load Progress Example</title> + <script type="text/javascript" src="../common/check_browser.js"></script> + <script type="text/javascript"> + // Check for Native Client support in the browser before the DOM loads. + var isValidBrowser = false; + var browserSupportStatus = 0; + var checker = new browser_version.BrowserChecker( + 14, // Minumum Chrome version. + navigator["appVersion"], + navigator["plugins"]); + checker.checkBrowser(); + + loadProgressModule = null; // Global application object. + statusText = 'NO-STATUS'; + + isValidBrowser = checker.getIsValidBrowser(); + browserSupportStatus = checker.getBrowserSupportStatus(); + + // Handler that gets called when the NaCl module starts loading. This + // event is always triggered when an <EMBED> tag has a MIME type of + // application/x-nacl. + function moduleDidStartLoad() { + appendToEventLog('loadstart'); + } + + // Progress event handler. |event| contains a couple of interesting + // properties that are used in this example: + // total The size of the NaCl module in bytes. Note that this value + // is 0 until |lengthComputable| is true. In particular, this + // value is 0 for the first 'progress' event. + // loaded The number of bytes loaded so far. + // lengthComputable A boolean indicating that the |total| field + // represents a valid length. + // + // event The ProgressEvent that triggered this handler. + function moduleLoadProgress(event) { + var loadPercent = 0.0; + var loadPercentString; + if (event.lengthComputable && event.total > 0) { + loadPercent = event.loaded / event.total * 100.0; + loadPercentString = loadPercent + '%'; + } else { + // The total length is not yet known. + loadPercent = -1.0; + loadPercentString = 'Computing...'; + } + appendToEventLog('progress: ' + loadPercentString + + ' (' + event.loaded + ' of ' + event.total + ' bytes)'); + } + + // Handler that gets called if an error occurred while loading the NaCl + // module. Note that the event does not carry any meaningful data about + // the error, you have to check lastError on the <EMBED> element to find + // out what happened. + function moduleLoadError() { + appendToEventLog('error: ' + loadProgressModule.lastError); + } + + // Handler that gets called if the NaCl module load is aborted. + function moduleLoadAbort() { + appendToEventLog('abort'); + } + + // When the NaCl module has loaded indicate success. + function moduleDidLoad() { + loadProgressModule = document.getElementById('load_progress'); + appendToEventLog('load'); + updateStatus('SUCCESS'); + } + + // Handler that gets called when the NaCl module loading has completed. + // You will always get one of these events, regardless of whether the NaCl + // module loaded successfully or not. For example, if there is an error + // during load, you will get an 'error' event and a 'loadend' event. Note + // that if the NaCl module loads successfully, you will get both a 'load' + // event and a 'loadend' event. + function moduleDidEndLoad() { + appendToEventLog('loadend'); + var lastError = event.target.lastError; + if (lastError == undefined || lastError.length == 0) { + lastError = '<none>'; + } + appendToEventLog('lastError: ' + lastError); + } + + + // Handle a message coming from the NaCl module. + function handleMessage(message_event) { + alert(message_event.data); + } + + // Set the global status message. Updates the 'status_field' element with + // the new text. + // opt_message The message text. If this is null or undefined, then + // attempt to set the element with id 'status_field' to the value of + // |statusText|. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + var statusField = document.getElementById('status_field'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + + // Append an event name to the 'event_log_field' element. Event names + // are separated by a <br> tag so they get listed one per line. + // logMessage The message to append to the log. + function appendToEventLog(logMessage) { + var eventLogField = document.getElementById('event_log_field'); + if (eventLogField.innerHTML.length == 0) { + eventLogField.innerHTML = logMessage; + } else { + eventLogField.innerHTML = eventLogField.innerHTML + + '<br />' + + logMessage; + } + } + </script> +</head> +<body> + +<h1>Native Client Load Event Example</h1> + +<h2>Event Log</h2> +<div id="event_log_field"></div> +<h2>Status</h2> +<div id="status_field">NO-STATUS</div> + +<div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener'); + listener.addEventListener('loadstart', moduleDidStartLoad, true); + listener.addEventListener('progress', moduleLoadProgress, true); + listener.addEventListener('error', moduleLoadError, true); + listener.addEventListener('abort', moduleLoadAbort, true); + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('loadend', moduleDidEndLoad, true); + listener.addEventListener('message', handleMessage, true); + + switch (browserSupportStatus) { + case browser_version.BrowserChecker.StatusValues.NACL_ENABLED: + appendToEventLog('Native Client plugin enabled.'); + break; + case browser_version.BrowserChecker.StatusValues.UNKNOWN_BROWSER: + updateStatus('UNKNOWN BROWSER'); + break; + case browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD: + appendToEventLog( + 'Chrome too old: You must use Chrome version 14 or later.'); + updateStatus('NEED CHROME 14 OR LATER'); + break; + case browser_version.BrowserChecker.StatusValues.NACL_NOT_ENABLED: + appendToEventLog( + 'NaCl disabled: Native Client is not enabled.<br>' + + 'Please go to <b>chrome://plugins</b> and enable Native Client ' + + 'plugin.'); + updateStatus('NaCl NOT ENABLED'); + break; + case browser_version.BrowserChecker.StatusValues.NOT_USING_SERVER: + appendToEventLog( + 'file: URL detected, please use a web server to host Native ' + + 'Client applications.'); + updateStatus('NaCl NOT ENABLED'); + default: + appendToEventLog('Unknown error: Unable to detect browser and/or ' + + 'Native Client support.'); + updateStatus('UNKNOWN ERROR'); + break; + } + </script> + + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <embed name="nacl_module" + id="load_progress" + width=0 height=0 + src="load_progress.nmf" + type="application/x-nacl" /> + + <script type="text/javascript"> + loadProgressModule = document.getElementById('load_progress'); + // Futher diagnose NaCl loading. + if (loadProgressModule == null || + typeof loadProgressModule.readyState == 'undefined') { + switch (browserSupportStatus) { + case browser_version.BrowserChecker.StatusValues.NACL_ENABLED: + // The NaCl plugin is enabled and running, it's likely that the flag + // isn't set. + appendToEventLog( + 'NaCl flag disabled: The Native Client flag is not enabled.<br>' + + 'Please go to <b>chrome://flags</b> enable Native Client and ' + + 'relaunch your browser. See also: ' + + '<a href="http://code.google.com/chrome/nativeclient/docs/' + + 'running.html">Running Web Applications that Use Native Client' + + '</a>'); + updateStatus('NaCl NOT ENABLED'); + break; + case browser_version.BrowserChecker.StatusValues.UNKNOWN_BROWSER: + appendToEventLog('Native Client applications are not supported by ' + + 'this browser.'); + break; + default: + appendToEventLog('Unknown error when loading Native Client ' + + 'application.'); + } + } + </script> +</div> +</body> +</html> diff --git a/native_client_sdk/src/examples/mouselock/build.scons b/native_client_sdk/src/examples/mouselock/build.scons new file mode 100644 index 0000000..f56186e --- /dev/null +++ b/native_client_sdk/src/examples/mouselock/build.scons @@ -0,0 +1,51 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='mouselock', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + CCFLAGS=['-pedantic', '-Werror'], + ) + +sources = ['mouselock.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'mouselock') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('mouselock') + +common_files = [ + 'check_browser.js', + ] +common_files = [ + os.path.join(os.path.dirname(os.getcwd()), 'common', common_file) + for common_file in common_files] + +app_files = [ + 'mouselock.html', + 'mouselock.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +common_dir = os.path.join(os.path.dirname(nacl_env['NACL_INSTALL_ROOT']), + 'common') +install_common = nacl_env.Install(dir=common_dir, source=common_files) +nacl_env.Alias('install', + source=[install_app, install_common, install_nexes]) diff --git a/native_client_sdk/src/examples/mouselock/mouselock.cc b/native_client_sdk/src/examples/mouselock/mouselock.cc new file mode 100644 index 0000000..b0e18fd --- /dev/null +++ b/native_client_sdk/src/examples/mouselock/mouselock.cc @@ -0,0 +1,329 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/mouselock/mouselock.h" + +#include <cmath> +#include <cstdlib> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +// Indicate the direction of the mouse location relative to the center of the +// view. These values are used to determine which 2D quadrant the needle lies +// in. +typedef enum { + kLeft = 0, + kRight = 1, + kUp = 2, + kDown = 3 +} MouseDirection; + +namespace { +const int kCentralSpotRadius = 5; +const uint32_t kReturnKeyCode = 13; +const uint32_t kBackgroundColor = 0xff606060; +const uint32_t kLockedForegroundColor = 0xfff08080; +const uint32_t kUnlockedForegroundColor = 0xff80f080; +} // namespace + +namespace mouselock { + +MouseLockInstance::~MouseLockInstance() { + free(background_scanline_); + background_scanline_ = NULL; +} + +bool MouseLockInstance::Init(uint32_t argc, + const char* argn[], + const char* argv[]) { + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | + PP_INPUTEVENT_CLASS_KEYBOARD); + return true; +} + +bool MouseLockInstance::HandleInputEvent(const pp::InputEvent& event) { + switch (event.GetType()) { + case PP_INPUTEVENT_TYPE_MOUSEDOWN: { + is_context_bound_ = false; + if (fullscreen_.IsFullscreen()) { + // Leaving fullscreen mode also unlocks the mouse if it was locked. + // In this case, the browser will call MouseLockLost() on this + // instance. + if (!fullscreen_.SetFullscreen(false)) { + Log("Could not leave fullscreen mode\n"); + } + } else { + if (!fullscreen_.SetFullscreen(true)) { + Log("Could not set fullscreen mode\n"); + } else { + pp::MouseInputEvent mouse_event(event); + if (mouse_event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_LEFT && + !mouse_locked_) { + LockMouse(callback_factory_.NewRequiredCallback( + &MouseLockInstance::DidLockMouse)); + } + } + } + return true; + } + + case PP_INPUTEVENT_TYPE_MOUSEMOVE: { + pp::MouseInputEvent mouse_event(event); + mouse_movement_ = mouse_event.GetMovement(); + Paint(); + return true; + } + + case PP_INPUTEVENT_TYPE_KEYDOWN: { + pp::KeyboardInputEvent key_event(event); + // Lock the mouse when the Enter key is pressed. + if (key_event.GetKeyCode() == kReturnKeyCode) { + if (mouse_locked_) { + UnlockMouse(); + } else { + LockMouse(callback_factory_.NewRequiredCallback( + &MouseLockInstance::DidLockMouse)); + } + } + return true; + } + + case PP_INPUTEVENT_TYPE_MOUSEUP: + case PP_INPUTEVENT_TYPE_MOUSEENTER: + case PP_INPUTEVENT_TYPE_MOUSELEAVE: + case PP_INPUTEVENT_TYPE_WHEEL: + case PP_INPUTEVENT_TYPE_RAWKEYDOWN: + case PP_INPUTEVENT_TYPE_KEYUP: + case PP_INPUTEVENT_TYPE_CHAR: + case PP_INPUTEVENT_TYPE_CONTEXTMENU: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: + case PP_INPUTEVENT_TYPE_IME_TEXT: + case PP_INPUTEVENT_TYPE_UNDEFINED: + default: + return false; + } +} + +void MouseLockInstance::DidChangeView(const pp::Rect& position, + const pp::Rect& clip) { + int width = position.size().width(); + int height = position.size().height(); + + // When entering into full-screen mode, DidChangeView() gets called twice. + // The first time, any 2D context will fail to bind to this pp::Instacne. + if (width == width_ && height == height_ && is_context_bound_) { + return; + } + width_ = width; + height_ = height; + + device_context_ = pp::Graphics2D(this, pp::Size(width_, height_), false); + waiting_for_flush_completion_ = false; + free(background_scanline_); + background_scanline_ = NULL; + is_context_bound_ = BindGraphics(device_context_); + if (!is_context_bound_) { + Log("Could not bind to 2D context\n"); + return; + } + background_scanline_ = static_cast<uint32_t*>( + malloc(width_ * sizeof(*background_scanline_))); + uint32_t* bg_pixel = background_scanline_; + for (int x = 0; x < width_; ++x) { + *bg_pixel++ = kBackgroundColor; + } + Paint(); +} + +void MouseLockInstance::MouseLockLost() { + if (mouse_locked_) { + mouse_locked_ = false; + Paint(); + } else { + PP_NOTREACHED(); + } +} + +void MouseLockInstance::DidLockMouse(int32_t result) { + mouse_locked_ = result == PP_OK; + mouse_movement_.set_x(0); + mouse_movement_.set_y(0); + Paint(); +} + +void MouseLockInstance::DidFlush(int32_t result) { + waiting_for_flush_completion_ = false; +} + +void MouseLockInstance::Paint() { + if (waiting_for_flush_completion_) { + return; + } + pp::ImageData image = PaintImage(width_, height_); + if (image.is_null()) { + Log("Could not create image data\n"); + return; + } + device_context_.ReplaceContents(&image); + waiting_for_flush_completion_ = true; + device_context_.Flush( + callback_factory_.NewRequiredCallback(&MouseLockInstance::DidFlush)); +} + +pp::ImageData MouseLockInstance::PaintImage(int width, int height) { + pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, + pp::Size(width, height), false); + if (image.is_null() || image.data() == NULL) + return image; + + ClearToBackground(&image); + uint32_t foreground_color = mouse_locked_ ? kLockedForegroundColor : + kUnlockedForegroundColor; + DrawCenterSpot(&image, foreground_color); + DrawNeedle(&image, foreground_color); + return image; +} + +void MouseLockInstance::ClearToBackground(pp::ImageData* image) { + if (image == NULL) { + Log("ClearToBackground with NULL image"); + return; + } + if (background_scanline_ == NULL) + return; + int image_height = image->size().height(); + int image_width = image->size().width(); + for (int y = 0; y < image_height; ++y) { + uint32_t* scanline = image->GetAddr32(pp::Point(0, y)); + memcpy(scanline, + background_scanline_, + image_width * sizeof(*background_scanline_)); + } +} + +void MouseLockInstance::DrawCenterSpot(pp::ImageData* image, + uint32_t spot_color) { + if (image == NULL) { + Log("DrawCenterSpot with NULL image"); + return; + } + // Draw the center spot. The ROI is bounded by the size of the spot, plus + // one pixel. + int center_x = image->size().width() / 2; + int center_y = image->size().height() / 2; + int region_of_interest_radius = kCentralSpotRadius + 1; + + for (int y = center_y - region_of_interest_radius; + y < center_y + region_of_interest_radius; + ++y) { + for (int x = center_x - region_of_interest_radius; + x < center_x + region_of_interest_radius; + ++x) { + if (GetDistance(x, y, center_x, center_y) < kCentralSpotRadius) { + *image->GetAddr32(pp::Point(x, y)) = spot_color; + } + } + } +} + +void MouseLockInstance::DrawNeedle(pp::ImageData* image, + uint32_t needle_color) { + if (image == NULL) { + Log("DrawNeedle with NULL image"); + return; + } + if (GetDistance(mouse_movement_.x(), mouse_movement_.y(), 0, 0) <= + kCentralSpotRadius) { + return; + } + + int abs_mouse_x = std::abs(mouse_movement_.x()); + int abs_mouse_y = std::abs(mouse_movement_.y()); + int center_x = image->size().width() / 2; + int center_y = image->size().height() / 2; + pp::Point vertex(mouse_movement_.x() + center_x, + mouse_movement_.y() + center_y); + pp::Point anchor_1; + pp::Point anchor_2; + MouseDirection direction = kLeft; + + if (abs_mouse_x >= abs_mouse_y) { + anchor_1.set_x(center_x); + anchor_1.set_y(center_y - kCentralSpotRadius); + anchor_2.set_x(center_x); + anchor_2.set_y(center_y + kCentralSpotRadius); + direction = (mouse_movement_.x() < 0) ? kLeft : kRight; + if (direction == kLeft) + anchor_1.swap(anchor_2); + } else { + anchor_1.set_x(center_x + kCentralSpotRadius); + anchor_1.set_y(center_y); + anchor_2.set_x(center_x - kCentralSpotRadius); + anchor_2.set_y(center_y); + direction = (mouse_movement_.y() < 0) ? kUp : kDown; + if (direction == kUp) + anchor_1.swap(anchor_2); + } + + for (int y = center_y - abs_mouse_y; y < center_y + abs_mouse_y; ++y) { + for (int x = center_x - abs_mouse_x; x < center_x + abs_mouse_x; ++x) { + bool within_bound_1 = + ((y - anchor_1.y()) * (vertex.x() - anchor_1.x())) > + ((vertex.y() - anchor_1.y()) * (x - anchor_1.x())); + bool within_bound_2 = + ((y - anchor_2.y()) * (vertex.x() - anchor_2.x())) < + ((vertex.y() - anchor_2.y()) * (x - anchor_2.x())); + bool within_bound_3 = + (direction == kUp && y < center_y) || + (direction == kDown && y > center_y) || + (direction == kLeft && x < center_x) || + (direction == kRight && x > center_x); + + if (within_bound_1 && within_bound_2 && within_bound_3) { + *image->GetAddr32(pp::Point(x, y)) = needle_color; + } + } + } +} + + +void MouseLockInstance::Log(const char* format, ...) { + va_list args; + va_start(args, format); + char buf[512]; + vsnprintf(buf, sizeof(buf) - 1, format, args); + buf[sizeof(buf) - 1] = '\0'; + va_end(args); + + pp::Var value(buf); + PostMessage(value); +} + +} // namespace mouselock + +// This object is the global object representing this plugin library as long +// as it is loaded. +class MouseLockModule : public pp::Module { + public: + MouseLockModule() : pp::Module() {} + virtual ~MouseLockModule() {} + + // Override CreateInstance to create your customized Instance object. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new mouselock::MouseLockInstance(instance); + } +}; + +namespace pp { + +// Factory function for your specialization of the Module object. +Module* CreateModule() { + return new MouseLockModule(); +} + +} // namespace pp + diff --git a/native_client_sdk/src/examples/mouselock/mouselock.h b/native_client_sdk/src/examples/mouselock/mouselock.h new file mode 100644 index 0000000..88650d6 --- /dev/null +++ b/native_client_sdk/src/examples/mouselock/mouselock.h @@ -0,0 +1,102 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <cmath> + +#include "ppapi/c/ppb_fullscreen.h" +#include "ppapi/c/ppb_input_event.h" +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/fullscreen.h" +#include "ppapi/cpp/mouse_lock.h" +#include "ppapi/cpp/graphics_2d.h" +#include "ppapi/cpp/image_data.h" +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/var.h" + +namespace mouselock { + +class MouseLockInstance : public pp::Instance, public pp::MouseLock { + public: + explicit MouseLockInstance(PP_Instance instance) + : pp::Instance(instance), + pp::MouseLock(this), + width_(0), + height_(0), + mouse_locked_(false), + waiting_for_flush_completion_(false), + callback_factory_(this), + fullscreen_(this), + is_context_bound_(false), + background_scanline_(NULL) { + } + virtual ~MouseLockInstance(); + + // Called by the browser when the NaCl module is loaded and all ready to go. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Called by the browser to handle incoming input events. + virtual bool HandleInputEvent(const pp::InputEvent& event); + + // Called whenever the in-browser window changes size. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip); + + // Called by the browser when mouselock is lost. This happens when the NaCl + // module exits fullscreen mode. + virtual void MouseLockLost(); + + private: + // Return the Cartesian distance between two points. + double GetDistance(int point_1_x, int point_1_y, + int point_2_x, int point_2_y) { + return sqrt(pow(static_cast<double>(point_1_x - point_2_x), 2) + + pow(static_cast<double>(point_1_y - point_2_y), 2)); + } + + // Called when mouse lock has been acquired. Used as a callback to + // pp::MouseLock.LockMouse(). + void DidLockMouse(int32_t result); + + // Called when the 2D context has been flushed to the browser window. Used + // as a callback to pp::Graphics2D.Flush(). + void DidFlush(int32_t result); + + // Creates a new paint buffer, paints it then flush it to the 2D context. If + // a flush is pending, this does nothing. + void Paint(); + + // Create a new pp::ImageData and paint the graphics that represent the mouse + // movement in it. Return the new pp::ImageData. + pp::ImageData PaintImage(int width, int height); + + // Fill the image with the backgroud color. + void ClearToBackground(pp::ImageData* image); + + // Draw a spot in |spot_color| in the center of the image. The radius of the + // spot is defined by a constant value in mouselock.cc + void DrawCenterSpot(pp::ImageData* image, uint32_t spot_color); + + // Draw the needle when the mouse is outside of the central spot. + void DrawNeedle(pp::ImageData* image, uint32_t needle_color); + + // Print the printf-style format to the "console" via PostMessage. + void Log(const char* format, ...); + + int width_; + int height_; + + bool mouse_locked_; + pp::Point mouse_movement_; + bool waiting_for_flush_completion_; + pp::CompletionCallbackFactory<MouseLockInstance> callback_factory_; + + pp::Fullscreen fullscreen_; + pp::Graphics2D device_context_; + bool is_context_bound_; + uint32_t* background_scanline_; +}; + +} // namespace mouselock diff --git a/native_client_sdk/src/examples/mouselock/mouselock.html b/native_client_sdk/src/examples/mouselock/mouselock.html new file mode 100644 index 0000000..eaa44dc --- /dev/null +++ b/native_client_sdk/src/examples/mouselock/mouselock.html @@ -0,0 +1,108 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> +<!-- +Copyright (c) 2011 The Native Client Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> +<head> + <meta http-equiv="Pragma" content="no-cache" /> + <meta http-equiv="Expires" content="-1" /> + <script type="text/javascript" src="../common/check_browser.js"></script> + <script> + // Check for Native Client support in the browser before the DOM loads. + var isValidBrowser = false; + var browserSupportStatus = 0; + // Fullscreen and mouselock support is in Chrome version 16. + var CHROME_MINIMUM_VERSION = 16; + + var checker = new browser_version.BrowserChecker( + CHROME_MINIMUM_VERSION, + navigator["appVersion"], + navigator["plugins"]); + checker.checkBrowser(); + + isValidBrowser = checker.getIsValidBrowser(); + browserSupportStatus = checker.getBrowserSupportStatus(); + + function handleMessage(message_event) { + console.log(message_event.data); + } + </script> + <title>Full-screen and Mouse-lock Example</title> +</head> +<body title="This tooltip should not be shown if the mouse is locked."> + <h1>Full-screen and Mouse-lock Example</h1> + <ul> + <li>There are two different kinds of fullscreen mode: "tab fullscreen" and + "browser fullscreen". + <ul> + <li>Tab fullscreen refers to when a tab enters fullscreen mode via the + JS or Pepper fullscreen API.</li> + <li>Browser fullscreen refers to the user putting the browser itself + into fullscreen mode from the UI (e.g., pressing F11).</li> + </ul> + <span style="font-weight:bold"> + NOTE: Mouse lock is only allowed in "tab fullscreen" mode. + </span> + </li> + <li>Lock mouse: + <ul> + <li>left click in the grey box; or</li> + <li>right click in the box to ensure that it is focused and + then press Enter key. (You could verify that the tooltip window is + dismissed properly by this second approach.)</li> + </ul> + </li> + <li>Unlock mouse voluntarily (i.e., NaCl module unlocks mouse): + <ul> + <li>press Enter.</li> + </ul> + </li> + <li>Unlock mouse involuntarily (i.e. Chrome unlocks mouse): + <ul> + <li>lose focus; or</li> + <li>press Esc key; or</li> + <li>exit from the "tab fullscreen" mode.</li> + </ul> + </li> + </ul> + <p>Clicking the mouse inside the grey square takes the NaCl module to/from + combined fullscreen and mouselock mode.</p> + <p>While in fullscreen, pressing Enter will exit/enter mouse lock mode.</p> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + if (browserSupportStatus == + browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD) { + alert('This example will only work on Chrome version ' + + CHROME_MINIMUM_VERSION + + ' or later.'); + } else { + var listener = document.getElementById('listener') + listener.addEventListener('message', handleMessage, true); + // Create two instances of the NaCl module. + listener.innerHTML = '<embed id="mouselock_view" ' + + 'type="application/x-nacl" ' + + 'src="mouselock.nmf" ' + + 'width="300" height="300" />'; + } + </script> + </div> +</body> +</html> diff --git a/native_client_sdk/src/examples/multithreaded_input_events/build.scons b/native_client_sdk/src/examples/multithreaded_input_events/build.scons new file mode 100644 index 0000000..00da894 --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/build.scons @@ -0,0 +1,41 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import nacl_utils +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='multithreaded_input_events', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['mt_input_events.cc', 'custom_events.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'mt_input_events') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('mt_input_events') + +app_files = [ + 'mt_input_events.html', + 'mt_input_events.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/multithreaded_input_events/custom_events.cc b/native_client_sdk/src/examples/multithreaded_input_events/custom_events.cc new file mode 100644 index 0000000..b281da5 --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/custom_events.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <sstream> + +#include "examples/multithreaded_input_events/custom_events.h" + +namespace event_queue { + +// Convert a given modifier to a descriptive string. Note that the actual +// declared type of modifier in each of the event classes is uint32_t, but it is +// expected to be interpreted as a bitfield of 'or'ed PP_InputEvent_Modifier +// values. +std::string ModifierToString(uint32_t modifier) { + std::string s; + if (modifier & kShiftKeyModifier) { + s += "shift "; + } + if (modifier & kControlKeyModifier) { + s += "ctrl "; + } + if (modifier & kAltKeyModifier) { + s += "alt "; + } + if (modifier & kMetaKeyModifer) { + s += "meta "; + } + if (modifier & kKeyPadModifier) { + s += "keypad "; + } + if (modifier & kAutoRepeatModifier) { + s += "autorepeat "; + } + if (modifier & kLeftButtonModifier) { + s += "left-button-down "; + } + if (modifier & kMiddleButtonModifier) { + s += "middle-button-down "; + } + if (modifier & kRightButtonModifier) { + s += "right-button-down "; + } + if (modifier & kCapsLockModifier) { + s += "caps-lock "; + } + if (modifier & kNumLockModifier) { + s += "num-lock "; + } + return s; +} + + +std::string KeyEvent::ToString() const { + std::ostringstream stream; + stream << " Key event:" + << " modifier:" << string_event_modifiers() + << " key_code:" << key_code_ + << " time:" << timestamp_ + << " text:" << text_ + << "\n"; + return stream.str(); +} + +std::string MouseEvent::ToString() const { + std::ostringstream stream; + stream << " Mouse event:" + << " modifier:" << string_event_modifiers() + << " button:" << MouseButtonToString(mouse_button_) + << " x:" << x_position_ + << " y:" << y_position_ + << " click_count:" << click_count_ + << " time:" << timestamp_ + << "\n"; + return stream.str(); +} + +std::string WheelEvent::ToString() const { + std::ostringstream stream; + stream << "Wheel event." + << " modifier:" << string_event_modifiers() + << " deltax:" << delta_x_ + << " deltay:" << delta_y_ + << " wheel_ticks_x:" << ticks_x_ + << " wheel_ticks_y:" << ticks_y_ + << " scroll_by_page: " << scroll_by_page_ + << "\n"; + return stream.str(); +} + +std::string MouseEvent::MouseButtonToString(MouseButton button) const { + switch (button) { + case kNone: + return "None"; + case kLeft: + return "Left"; + case kMiddle: + return "Middle"; + case kRight: + return "Right"; + default: + std::ostringstream stream; + stream << "Unrecognized (" + << static_cast<int32_t>(button) + << ")"; + return stream.str(); + } +} + +} // end namespace + diff --git a/native_client_sdk/src/examples/multithreaded_input_events/custom_events.h b/native_client_sdk/src/examples/multithreaded_input_events/custom_events.h new file mode 100644 index 0000000..029fb7a --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/custom_events.h @@ -0,0 +1,133 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CUSTOM_EVENTS_H +#define CUSTOM_EVENTS_H + +#include <stdint.h> +#include <string> +namespace event_queue { + +// These functions and classes are used to define a non-Pepper set of +// events. This is typical of what many developers might do, since it +// would be common to convert a Pepper event into some other more +// application-specific type of event (SDL, Qt, etc.). + +// Constants used for Event::event_modifers_ (which is an int) +// are given below. Use powers of 2 so we can use bitwise AND/OR operators. +const uint32_t kShiftKeyModifier = 1 << 0; +const uint32_t kControlKeyModifier = 1 << 1; +const uint32_t kAltKeyModifier = 1 << 2; +const uint32_t kMetaKeyModifer = 1 << 3; +const uint32_t kKeyPadModifier = 1 << 4; +const uint32_t kAutoRepeatModifier = 1 << 5; +const uint32_t kLeftButtonModifier = 1 << 6; +const uint32_t kMiddleButtonModifier = 1 << 7; +const uint32_t kRightButtonModifier = 1 << 8; +const uint32_t kCapsLockModifier = 1 << 9; +const uint32_t kNumLockModifier = 1 << 10; + +std::string ModifierToString(uint32_t modifier); + +// Abstract base class for an Event -- ToString() is not defined. +// With polymorphism, we can have a collection of Event* and call +// ToString() on each one to be able to display the details of each +// event. +class Event { + public: + // Constructor for the base class. + // |modifiers| is an int that uses bit fields to set specific + // changes, such as the Alt key, specific button, etc. See + // ModifierToString() and constants defined in custom_events.cc. + explicit Event(uint32_t modifiers) + : event_modifiers_(modifiers) {} + uint32_t event_modifiers() const {return event_modifiers_;} + std::string string_event_modifiers() const { + return ModifierToString(event_modifiers_); + } + // Convert the WheelEvent to a string + virtual std::string ToString() const = 0; + virtual ~Event() {} + + private: + uint32_t event_modifiers_; +}; + +// Class for a keyboard event. +class KeyEvent : public Event { + public: + // KeyEvent Constructor. |modifiers| is passed to Event base class. + // |keycode| is the ASCII value, |time| is a timestamp, + // |text| is the value as a string. + KeyEvent(uint32_t modifiers, uint32_t keycode, double time, + std::string text) : + Event(modifiers), key_code_(keycode), + timestamp_(time), text_(text) {} + // Convert the WheelEvent to a string + virtual std::string ToString() const; + + private: + uint32_t key_code_; + double timestamp_; + std::string text_; +}; +class MouseEvent : public Event { + public: + // Specify a mouse button, with kNone available for initialization. + enum MouseButton {kNone, kLeft, kMiddle, kRight}; + + // MouseEvent Constructor. |modifiers| is passed to Event base class. + // |button| specifies which button + // |xpos| and |ypos| give the location, + // |clicks| is how many times this same |xpos|,|ypos| + // has been clicked in a row. |time| is a timestamp, + MouseEvent(uint32_t modifiers, MouseButton button, uint32_t xpos, + uint32_t ypos, uint32_t clicks, double time) + : Event(modifiers), mouse_button_(button), + x_position_(xpos), y_position_(ypos), + click_count_(clicks), timestamp_(time) {} + // Convert the WheelEvent to a string + virtual std::string ToString() const; + + private: + MouseButton mouse_button_; + uint32_t x_position_; + uint32_t y_position_; + uint32_t click_count_; + double timestamp_; + + std::string MouseButtonToString(MouseButton button) const; +}; + + +class WheelEvent : public Event { + public: + // WheelEvent Constructor. |modifiers| is passed to Event base class. + // |xticks| and |yticks| specify number of mouse wheel ticks. + // |scroll_by_page| indicates if we have scrolled past the current + // page. |time| is a timestamp, + WheelEvent(int modifiers, uint32_t dx, uint32_t dy, + uint32_t xticks, uint32_t yticks, bool scroll_by_page, + float time) : + Event(modifiers), delta_x_(dx), delta_y_(dy), + ticks_x_(xticks), ticks_y_(yticks), + scroll_by_page_(scroll_by_page), timestamp_(time) {} + // Convert the WheelEvent to a string + virtual std::string ToString() const; + + private: + uint32_t delta_x_; + uint32_t delta_y_; + uint32_t ticks_x_; + uint32_t ticks_y_; + bool scroll_by_page_; + double timestamp_; +}; + + + +} // end namespace + +#endif // CUSTOM_EVENTS_H + diff --git a/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.cc b/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.cc new file mode 100644 index 0000000..a392ba2 --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.cc @@ -0,0 +1,323 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// C headers +#include <cassert> +#include <cstdio> + +// C++ headers +#include <sstream> +#include <string> + +#include "examples/multithreaded_input_events/custom_events.h" +#include "examples/multithreaded_input_events/shared_queue.h" +#include "examples/multithreaded_input_events/thread_safe_ref_count.h" + +// NaCl +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/point.h" +#include "ppapi/cpp/var.h" + +namespace event_queue { +const char* const kDidChangeView = "DidChangeView"; +const char* const kHandleInputEvent = "DidHandleInputEvent"; +const char* const kDidChangeFocus = "DidChangeFocus"; +const char* const kHaveFocus = "HaveFocus"; +const char* const kDontHaveFocus = "DontHaveFocus"; +const char* const kCancelMessage = "CANCEL"; + +// Convert a pepper inputevent modifier value into a +// custom event modifier. +unsigned int ConvertEventModifier(uint32_t pp_modifier) { + unsigned int custom_modifier = 0; + if (pp_modifier & PP_INPUTEVENT_MODIFIER_SHIFTKEY) { + custom_modifier |= kShiftKeyModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_CONTROLKEY) { + custom_modifier |= kControlKeyModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_ALTKEY) { + custom_modifier |= kAltKeyModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_METAKEY) { + custom_modifier |= kMetaKeyModifer; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_ISKEYPAD) { + custom_modifier |= kKeyPadModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_ISAUTOREPEAT) { + custom_modifier |= kAutoRepeatModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) { + custom_modifier |= kLeftButtonModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN) { + custom_modifier |= kMiddleButtonModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_RIGHTBUTTONDOWN) { + custom_modifier |= kRightButtonModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY) { + custom_modifier |= kCapsLockModifier; + } + if (pp_modifier & PP_INPUTEVENT_MODIFIER_NUMLOCKKEY) { + custom_modifier |= kNumLockModifier; + } + return custom_modifier; +} + +class EventInstance : public pp::Instance { + public: + explicit EventInstance(PP_Instance instance) + : pp::Instance(instance), + event_thread_(NULL), + callback_factory_(this) { + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL); + RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); + } + + // Not guaranteed to be called in Pepper, but a good idea to cancel the + // queue and signal to workers to die if it is called. + virtual ~EventInstance() { + CancelQueueAndWaitForWorker(); + } + + // Create the 'worker thread'. + bool Init(uint32_t argc, const char* argn[], const char* argv[]) { + pthread_create(&event_thread_, NULL, ProcessEventOnWorkerThread, this); + return true; + } + + /// Clicking outside of the instance's bounding box + /// will create a DidChangeFocus event (the NaCl instance is + /// out of focus). Clicking back inside the instance's + /// bounding box will create another DidChangeFocus event + /// (the NaCl instance is back in focus). The default is + /// that the instance is out of focus. + void DidChangeFocus(bool focus) { + PostMessage(pp::Var(kDidChangeFocus)); + if (focus == true) { + PostMessage(pp::Var(kHaveFocus)); + } else { + PostMessage(pp::Var(kDontHaveFocus)); + } + } + + /// Scrolling the mouse wheel causes a DidChangeView event. + void DidChangeView(const pp::Rect& position, + const pp::Rect& clip) { + PostMessage(pp::Var(kDidChangeView)); + } + + /// Called by the browser to handle the postMessage() call in Javascript. + /// Detects which method is being called from the message contents, and + /// calls the appropriate function. Posts the result back to the browser + /// asynchronously. + /// @param[in] var_message The message posted by the browser. The only + /// supported message is |kCancelMessage|. If we receive this, we + /// cancel the shared queue. + virtual void HandleMessage(const pp::Var& var_message) { + std::string message = var_message.AsString(); + if (kCancelMessage == message) { + std::string reply = "Received cancel : only Focus events will be " + "displayed. Worker thread for mouse/wheel/keyboard will exit."; + PostMessage(pp::Var(reply)); + printf("Calling cancel queue\n"); + CancelQueueAndWaitForWorker(); + } + } + + // HandleInputEvent operates on the main Pepper thread. Here we + // illustrate copying the Pepper input event to our own custom event type. + // Since we need to use Pepper API calls to convert it, we must do the + // conversion on the main thread. Once we have converted it to our own + // event type, we push that into a thread-safe queue and quickly return. + // The worker thread can process the custom event and do whatever + // (possibly slow) things it wants to do without making the browser + // become unresponsive. + // We dynamically allocate a sub-class of our custom event (Event) + // so that the queue can contain an Event*. + virtual bool HandleInputEvent(const pp::InputEvent& event) { + Event* event_ptr = NULL; + switch (event.GetType()) { + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: + case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: + case PP_INPUTEVENT_TYPE_IME_TEXT: + // these cases are not handled...fall through below... + case PP_INPUTEVENT_TYPE_UNDEFINED: + break; + case PP_INPUTEVENT_TYPE_MOUSEDOWN: + case PP_INPUTEVENT_TYPE_MOUSEUP: + case PP_INPUTEVENT_TYPE_MOUSEMOVE: + case PP_INPUTEVENT_TYPE_MOUSEENTER: + case PP_INPUTEVENT_TYPE_MOUSELEAVE: + { + pp::MouseInputEvent mouse_event(event); + PP_InputEvent_MouseButton pp_button = mouse_event.GetButton(); + MouseEvent::MouseButton mouse_button = MouseEvent::kNone; + switch (pp_button) { + case PP_INPUTEVENT_MOUSEBUTTON_NONE: + mouse_button = MouseEvent::kNone; + break; + case PP_INPUTEVENT_MOUSEBUTTON_LEFT: + mouse_button = MouseEvent::kLeft; + break; + case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE: + mouse_button = MouseEvent::kMiddle; + break; + case PP_INPUTEVENT_MOUSEBUTTON_RIGHT: + mouse_button = MouseEvent::kRight; + break; + } + event_ptr = new MouseEvent( + ConvertEventModifier(mouse_event.GetModifiers()), + mouse_button, mouse_event.GetPosition().x(), + mouse_event.GetPosition().y(), mouse_event.GetClickCount(), + mouse_event.GetTimeStamp()); + } + break; + case PP_INPUTEVENT_TYPE_WHEEL: + { + pp::WheelInputEvent wheel_event(event); + event_ptr = new WheelEvent( + ConvertEventModifier(wheel_event.GetModifiers()), + wheel_event.GetDelta().x(), wheel_event.GetDelta().y(), + wheel_event.GetTicks().x(), wheel_event.GetTicks().y(), + wheel_event.GetScrollByPage(), wheel_event.GetTimeStamp()); + } + break; + case PP_INPUTEVENT_TYPE_RAWKEYDOWN: + case PP_INPUTEVENT_TYPE_KEYDOWN: + case PP_INPUTEVENT_TYPE_KEYUP: + case PP_INPUTEVENT_TYPE_CHAR: + case PP_INPUTEVENT_TYPE_CONTEXTMENU: + { + pp::KeyboardInputEvent key_event(event); + event_ptr = new KeyEvent( + ConvertEventModifier(key_event.GetModifiers()), + key_event.GetKeyCode(), key_event.GetTimeStamp(), + key_event.GetCharacterText().DebugString()); + } + break; + default: + { + // For any unhandled events, send a message to the browser + // so that the user is aware of these and can investigate. + std::stringstream oss; + oss << "Default (unhandled) event, type=" << event.GetType(); + PostMessage(oss.str()); + } + break; + } + event_queue_.Push(event_ptr); + return true; + } + + // Return an event from the thread-safe queue, waiting for a new event + // to occur if the queue is empty. Set |was_queue_cancelled| to indicate + // whether the queue was cancelled. If it was cancelled, then the + // Event* will be NULL. + const Event* GetEventFromQueue(bool *was_queue_cancelled) { + Event* event; + QueueGetResult result = event_queue_.GetItem(&event, kWait); + if (result == kQueueWasCancelled) { + *was_queue_cancelled = true; + return NULL; + } + *was_queue_cancelled = false; + return event; + } + + // This method is called from the worker thread using CallOnMainThread. + // It is not static, and allows PostMessage to be called. + void* PostStringToBrowser(int32_t result, std::string data_to_send) { + PostMessage(pp::Var(data_to_send)); + return 0; + } + + // |ProcessEventOnWorkerThread| is a static method that is run + // by a thread. It pulls events from the queue, converts + // them to a string, and calls CallOnMainThread so that + // PostStringToBrowser will be called, which will call PostMessage + // to send the converted event back to the browser. + static void* ProcessEventOnWorkerThread(void* param) { + EventInstance* event_instance = static_cast<EventInstance*>(param); + while (1) { + // Grab a generic Event* so that down below we can call + // event->ToString(), which will use the correct virtual method + // to convert the event to a string. This 'conversion' is + // the 'work' being done on the worker thread. In an application + // the work might involve changing application state based on + // the event that was processed. + bool queue_cancelled; + const Event* event = event_instance->GetEventFromQueue(&queue_cancelled); + if (queue_cancelled) { + printf("Queue was cancelled, worker thread exiting\n"); + pthread_exit(NULL); + } + std::string event_string = event->ToString(); + delete event; + // Need to invoke callback on main thread. + pp::Module::Get()->core()->CallOnMainThread( + 0, + event_instance->callback_factory().NewCallback( + &EventInstance::PostStringToBrowser, + event_string)); + } // end of while loop. + return 0; + } + + // Return the callback factory. + // Allows the static method (ProcessEventOnWorkerThread) to use + // the |event_instance| pointer to get the factory. + pp::CompletionCallbackFactory<EventInstance, ThreadSafeRefCount>& + callback_factory() { + return callback_factory_; + } + + private: + // Cancels the queue (which will cause the thread to exit). + // Wait for the thread. Set |event_thread_| to NULL so we only + // execute the body once. + void CancelQueueAndWaitForWorker() { + if (event_thread_) { + event_queue_.CancelQueue(); + pthread_join(event_thread_, NULL); + event_thread_ = NULL; + } + } + pthread_t event_thread_; + LockingQueue<Event*> event_queue_; + pp::CompletionCallbackFactory<EventInstance, ThreadSafeRefCount> + callback_factory_; +}; + +// The EventModule provides an implementation of pp::Module that creates +// EventInstance objects when invoked. This is part of the glue code that makes +// our example accessible to ppapi. +class EventModule : public pp::Module { + public: + EventModule() : pp::Module() {} + virtual ~EventModule() {} + + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new EventInstance(instance); + } +}; + +} // namespace + +// Implement the required pp::CreateModule function that creates our specific +// kind of Module (in this case, EventModule). This is part of the glue code +// that makes our example accessible to ppapi. +namespace pp { + Module* CreateModule() { + return new event_queue::EventModule(); + } +} + diff --git a/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.html b/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.html new file mode 100644 index 0000000..7e85e1c --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2011 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> +<head> + <title>Input Events</title> + + <script type="text/javascript"> + var kMaxArraySize = 20; + var messageArray = new Array(); + var eventModule = null; + + function $(id) { + return document.getElementById(id); + } + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + eventModule = document.getElementById('event_module'); + } + + function receiveMessage(message) { + // Show last |kMaxArraySize| events in html. + messageArray.push(message.data); + if (messageArray.length > kMaxArraySize) { + messageArray.shift(); + } + var newData = messageArray.join('<BR>'); + document.getElementById('eventString').innerHTML = newData; + // Print event to console. + console.log(message.data); + } + function cancelQueue() { + if (eventModule == null) { + console.log('Module is not loaded.'); + return; + } + eventModule.postMessage('CANCEL'); + } + </script> +</head> +<body> +<h1>InputEvent Handling Example</h1> + <div id="listener"> + <script type="text/javascript"> + $('listener').addEventListener('message', receiveMessage, true); + $('listener').addEventListener('load', moduleDidLoad, true); + </script> + <button onclick="cancelQueue()">Kill worker thread and queue</button> + <p/> + <embed name="nacl_module" + id="event_module" + width=400 height=400 + src="mt_input_events.nmf" + type="application/x-nacl" + style="background-color:gray" /> + </div> +<p> +This example demonstrates handling of input events in PPAPI.</p> +<p> +Each time an input event happens in the context of the gray box, +the main thread in the embedded NaCl module converts it from a Pepper input +event to a non-Pepper event and puts this custom event onto a shared queue. +A worker thread in the embedded NaCl module reads events from the queue, +and converts each event to a string and then uses CallOnMainThread to post a +message describing the event back to JavaScript, which prints a message to the +JavaScript console in Chrome and to a string on the page.</p> +<p> +If you press the 'Kill worker thread and queue' button, then the main thread +(which puts events on the queue) will call CancelQueue, indicating that the +main thread will no longer put events on the queue. When the worker sees that +the shared queue has been cancelled, the worker thread will terminate.</p> +<h2>Events</h2> +<pre><p><b id='eventString'>None</b></p> +</pre> +</body> +</html> diff --git a/native_client_sdk/src/examples/multithreaded_input_events/shared_queue.h b/native_client_sdk/src/examples/multithreaded_input_events/shared_queue.h new file mode 100644 index 0000000..22ff7ae --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/shared_queue.h @@ -0,0 +1,137 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SHARED_QUEUE_H +#define SHARED_QUEUE_H + +#include <pthread.h> +#include <cassert> +#include <deque> + +#include "examples/multithreaded_input_events/thread_safe_ref_count.h" + +namespace event_queue { + +// This file provides a queue that uses a mutex and condition variable so that +// one thread can put pointers into the queue and another thread can pull items +// out of the queue. + +const int kPthreadMutexSuccess = 0; + +// Specifies whether we want to wait for the queue. +enum QueueWaitingFlag { + kWait = 0, + kDontWait +}; + +// Indicates if we got an item, did not wait, or if the queue was cancelled. +enum QueueGetResult { + kReturnedItem = 0, + kDidNotWait = 1, + kQueueWasCancelled +}; + +// LockingQueue contains a collection of <T>, such as a collection of +// objects or pointers. The Push() method is used to add items to the +// queue in a thread-safe manner. The GetItem() is used to retrieve +// items from the queue in a thread-safe manner. +template <class T> +class LockingQueue { + public: + LockingQueue() : quit_(false) { + int result = pthread_mutex_init(&queue_mutex_, NULL); + assert(result == 0); + result = pthread_cond_init(&queue_condition_var_, NULL); + assert(result == 0); + } + ~LockingQueue() { + pthread_mutex_destroy(&queue_mutex_); + } + + // The producer (who instantiates the queue) calls this to tell the + // consumer that the queue is no longer being used. + void CancelQueue() { + ScopedLock scoped_mutex(&queue_mutex_); + quit_ = true; + // Signal the condition var so that if a thread is waiting in + // GetItem the thread will wake up and see that the queue has + // been cancelled. + pthread_cond_signal(&queue_condition_var_); + } + + // The consumer calls this to see if the queue has been cancelled by + // the producer. If so, the thread should not call GetItem and may + // need to terminate -- i.e. in a case where the producer created + // the consumer thread. + bool IsCancelled() { + ScopedLock scoped_mutex(&queue_mutex_); + return quit_; + } + + // Grabs the mutex and pushes a new item to the end of the queue if the + // queue is not full. Signals the condition variable so that a thread + // that is waiting will wake up and grab the item. + void Push(const T& item) { + ScopedLock scoped_mutex(&queue_mutex_); + the_queue_.push_back(item); + pthread_cond_signal(&queue_condition_var_); + } + + // Tries to pop the front element from the queue; returns an enum: + // kReturnedItem if an item is returned in |item_ptr|, + // kDidNotWait if |wait| was kDontWait and the queue was empty, + // kQueueWasCancelled if the producer called CancelQueue(). + // If |wait| is kWait, GetItem will wait to return until the queue + // contains an item (unless the queue is cancelled). + QueueGetResult GetItem(T* item_ptr, QueueWaitingFlag wait) { + // Because we use both pthread_mutex_lock and pthread_cond_wait, + // we directly use the mutex instead of using ScopedLock. + ScopedLock scoped_mutex(&queue_mutex_); + // Use a while loop to get an item. If the user does not want to wait, + // we will exit from the loop anyway, unlocking the mutex. + // If the user does want to wait, we will wait for pthread_cond_wait, + // and the while loop will check is_empty_no_locking() one more + // time so that a spurious wake-up of pthread_cond_wait is handled. + // If |quit_| has been set, break out of the loop. + while (!quit_ && is_empty_no_locking()) { + // If user doesn't want to wait, return... + if (kDontWait == wait) { + return kDidNotWait; + } + // Wait for signal to occur. + pthread_cond_wait(&queue_condition_var_, &queue_mutex_); + } + // Check to see if quit_ woke us up + if (quit_) { + return kQueueWasCancelled; + } + + // At this point, the queue was either not empty or, if it was empty, + // we called pthread_cond_wait (which released the mutex, waited for the + // signal to occur, and then atomically reacquired the mutex). + // Thus, if we are here, the queue cannot be empty because we either + // had the mutex and verified it was not empty, or we waited for the + // producer to put an item in and signal a single thread (us). + T& item = the_queue_.front(); + *item_ptr = item; + the_queue_.pop_front(); + return kReturnedItem; + } + + private: + std::deque<T> the_queue_; + bool quit_; + pthread_mutex_t queue_mutex_; + pthread_cond_t queue_condition_var_; + + // This is used by methods that already have the lock. + bool is_empty_no_locking() const { + return the_queue_.empty(); + } +}; + +} // end of unnamed namespace + +#endif // SHARED_QUEUE_H + diff --git a/native_client_sdk/src/examples/multithreaded_input_events/thread_safe_ref_count.h b/native_client_sdk/src/examples/multithreaded_input_events/thread_safe_ref_count.h new file mode 100644 index 0000000..f4e6beb --- /dev/null +++ b/native_client_sdk/src/examples/multithreaded_input_events/thread_safe_ref_count.h @@ -0,0 +1,63 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THREAD_SAFE_REF_COUNT_H +#define THREAD_SAFE_REF_COUNT_H + +#include <pthread.h> +#include <cassert> + +const int kPthreadMutexSuccess = 0; + +namespace event_queue { +// Some synchronization helper classes. + +class ScopedLock { + public: + explicit ScopedLock(pthread_mutex_t* mutex) + : mutex_(mutex) { + if (pthread_mutex_lock(mutex_) != kPthreadMutexSuccess) { + mutex_ = NULL; + } + } + ~ScopedLock() { + if (mutex_ != NULL) { + pthread_mutex_unlock(mutex_); + } + } + private: + ScopedLock& operator=(const ScopedLock&); // Not implemented, do not use. + ScopedLock(const ScopedLock&); // Not implemented, do not use. + pthread_mutex_t* mutex_; // Weak reference, passed in to constructor. +}; + +class ThreadSafeRefCount { + public: + ThreadSafeRefCount() + : ref_(0) { + Init(); + } + + void Init() { + pthread_mutex_init(&mutex_, NULL); + } + + int32_t AddRef() { + ScopedLock s(&mutex_); + return ++ref_; + } + + int32_t Release() { + ScopedLock s(&mutex_); + return --ref_; + } + + private: + int32_t ref_; + pthread_mutex_t mutex_; +}; + +} // namespace +#endif // THREAD_SAFE_REF_COUNT_H + diff --git a/native_client_sdk/src/examples/pi_generator/build.scons b/native_client_sdk/src/examples/pi_generator/build.scons new file mode 100644 index 0000000..8148242 --- /dev/null +++ b/native_client_sdk/src/examples/pi_generator/build.scons @@ -0,0 +1,40 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='pi_generator', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['pi_generator.cc', 'pi_generator_module.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'pi_generator') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('pi_generator') + +app_files = [ + 'pi_generator.html', + 'pi_generator.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator.cc b/native_client_sdk/src/examples/pi_generator/pi_generator.cc new file mode 100644 index 0000000..f5e6e4d --- /dev/null +++ b/native_client_sdk/src/examples/pi_generator/pi_generator.cc @@ -0,0 +1,240 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/pi_generator/pi_generator.h" + +#include <stdio.h> +#include <stdlib.h> +#include <cassert> +#include <cmath> +#include <cstring> +#include <string> +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/var.h" + +namespace { +const int kPthreadMutexSuccess = 0; +const char* const kPaintMethodId = "paint"; +const double kInvalidPiValue = -1.0; +const int kMaxPointCount = 1000000000; // The total number of points to draw. +const uint32_t kOpaqueColorMask = 0xff000000; // Opaque pixels. +const uint32_t kRedMask = 0xff0000; +const uint32_t kBlueMask = 0xff; +const uint32_t kRedShift = 16; +const uint32_t kBlueShift = 0; + +// This is called by the browser when the 2D context has been flushed to the +// browser window. +void FlushCallback(void* data, int32_t result) { + static_cast<pi_generator::PiGenerator*>(data)->set_flush_pending(false); +} +} // namespace + +namespace pi_generator { + +// A small helper RAII class that implementes a scoped pthread_mutex lock. +class ScopedMutexLock { + public: + explicit ScopedMutexLock(pthread_mutex_t* mutex) : mutex_(mutex) { + if (pthread_mutex_lock(mutex_) != kPthreadMutexSuccess) { + mutex_ = NULL; + } + } + ~ScopedMutexLock() { + if (mutex_) + pthread_mutex_unlock(mutex_); + } + bool is_valid() const { + return mutex_ != NULL; + } + private: + pthread_mutex_t* mutex_; // Weak reference. +}; + +// A small helper RAII class used to acquire and release the pixel lock. +class ScopedPixelLock { + public: + explicit ScopedPixelLock(PiGenerator* image_owner) + : image_owner_(image_owner), pixels_(image_owner->LockPixels()) {} + + ~ScopedPixelLock() { + pixels_ = NULL; + image_owner_->UnlockPixels(); + } + + uint32_t* pixels() const { + return pixels_; + } + private: + PiGenerator* image_owner_; // Weak reference. + uint32_t* pixels_; // Weak reference. + + ScopedPixelLock(); // Not implemented, do not use. +}; + +PiGenerator::PiGenerator(PP_Instance instance) + : pp::Instance(instance), + graphics_2d_context_(NULL), + pixel_buffer_(NULL), + flush_pending_(false), + quit_(false), + compute_pi_thread_(0), + pi_(0.0) { + pthread_mutex_init(&pixel_buffer_mutex_, NULL); +} + +PiGenerator::~PiGenerator() { + quit_ = true; + if (compute_pi_thread_) { + pthread_join(compute_pi_thread_, NULL); + } + DestroyContext(); + // The ComputePi() thread should be gone by now, so there is no need to + // acquire the mutex for |pixel_buffer_|. + delete pixel_buffer_; + pthread_mutex_destroy(&pixel_buffer_mutex_); +} + +void PiGenerator::DidChangeView(const pp::Rect& position, + const pp::Rect& clip) { + if (position.size().width() == width() && + position.size().height() == height()) + return; // Size didn't change, no need to update anything. + + // Create a new device context with the new size. + DestroyContext(); + CreateContext(position.size()); + // Delete the old pixel buffer and create a new one. + ScopedMutexLock scoped_mutex(&pixel_buffer_mutex_); + delete pixel_buffer_; + pixel_buffer_ = NULL; + if (graphics_2d_context_ != NULL) { + pixel_buffer_ = new pp::ImageData(this, + PP_IMAGEDATAFORMAT_BGRA_PREMUL, + graphics_2d_context_->size(), + false); + } +} + +bool PiGenerator::Init(uint32_t argc, const char* argn[], const char* argv[]) { + pthread_create(&compute_pi_thread_, NULL, ComputePi, this); + return true; +} + +uint32_t* PiGenerator::LockPixels() { + void* pixels = NULL; + // Do not use a ScopedMutexLock here, since the lock needs to be held until + // the matching UnlockPixels() call. + if (pthread_mutex_lock(&pixel_buffer_mutex_) == kPthreadMutexSuccess) { + if (pixel_buffer_ != NULL && !pixel_buffer_->is_null()) { + pixels = pixel_buffer_->data(); + } + } + return reinterpret_cast<uint32_t*>(pixels); +} + +void PiGenerator::HandleMessage(const pp::Var& var_message) { + if (!var_message.is_string()) { + PostMessage(pp::Var(kInvalidPiValue)); + } + std::string message = var_message.AsString(); + if (message == kPaintMethodId) { + Paint(); + } else { + PostMessage(pp::Var(kInvalidPiValue)); + } +} + +void PiGenerator::UnlockPixels() const { + pthread_mutex_unlock(&pixel_buffer_mutex_); +} + +void PiGenerator::Paint() { + ScopedMutexLock scoped_mutex(&pixel_buffer_mutex_); + if (!scoped_mutex.is_valid()) { + return; + } + FlushPixelBuffer(); + // Post the current estimate of Pi back to the browser. + pp::Var pi_estimate(pi()); + // Paint() is called on the main thread, so no need for CallOnMainThread() + // here. It's OK to just post the message. + PostMessage(pi_estimate); +} + +void PiGenerator::CreateContext(const pp::Size& size) { + ScopedMutexLock scoped_mutex(&pixel_buffer_mutex_); + if (!scoped_mutex.is_valid()) { + return; + } + if (IsContextValid()) + return; + graphics_2d_context_ = new pp::Graphics2D(this, size, false); + if (!BindGraphics(*graphics_2d_context_)) { + printf("Couldn't bind the device context\n"); + } +} + +void PiGenerator::DestroyContext() { + ScopedMutexLock scoped_mutex(&pixel_buffer_mutex_); + if (!scoped_mutex.is_valid()) { + return; + } + if (!IsContextValid()) + return; + delete graphics_2d_context_; + graphics_2d_context_ = NULL; +} + +void PiGenerator::FlushPixelBuffer() { + if (!IsContextValid()) + return; + // Note that the pixel lock is held while the buffer is copied into the + // device context and then flushed. + graphics_2d_context_->PaintImageData(*pixel_buffer_, pp::Point()); + if (flush_pending()) + return; + set_flush_pending(true); + graphics_2d_context_->Flush(pp::CompletionCallback(&FlushCallback, this)); +} + +void* PiGenerator::ComputePi(void* param) { + int count = 0; // The number of points put inside the inscribed quadrant. + unsigned int seed = 1; + PiGenerator* pi_generator = static_cast<PiGenerator*>(param); + srand(seed); + for (int i = 1; i <= kMaxPointCount && !pi_generator->quit(); ++i) { + ScopedPixelLock scoped_pixel_lock(pi_generator); + uint32_t* pixel_bits = scoped_pixel_lock.pixels(); + if (pixel_bits == NULL) { + // Note that if the pixel buffer never gets initialized, this won't ever + // paint anything. Which is probably the right thing to do. Also, this + // clause means that the image will not get the very first few Pi dots, + // since it's possible that this thread starts before the pixel buffer is + // initialized. + continue; + } + double x = static_cast<double>(rand_r(&seed)) / RAND_MAX; + double y = static_cast<double>(rand_r(&seed)) / RAND_MAX; + double distance = sqrt(x * x + y * y); + int px = x * pi_generator->width(); + int py = (1.0 - y) * pi_generator->height(); + uint32_t color = pixel_bits[pi_generator->width() * py + px]; + if (distance < 1.0) { + // Set color to blue. + ++count; + pi_generator->pi_ = 4.0 * count / i; + color += 4 << kBlueShift; + color &= kBlueMask; + } else { + // Set color to red. + color += 4 << kRedShift; + color &= kRedMask; + } + pixel_bits[pi_generator->width() * py + px] = color | kOpaqueColorMask; + } + return 0; +} + +} // namespace pi_generator diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator.h b/native_client_sdk/src/examples/pi_generator/pi_generator.h new file mode 100644 index 0000000..beecf41 --- /dev/null +++ b/native_client_sdk/src/examples/pi_generator/pi_generator.h @@ -0,0 +1,117 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_PI_GENERATOR_PI_GENERATOR_H_ +#define EXAMPLES_PI_GENERATOR_PI_GENERATOR_H_ + +#include <pthread.h> +#include <map> +#include <vector> +#include "ppapi/cpp/graphics_2d.h" +#include "ppapi/cpp/image_data.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/size.h" + +namespace pi_generator { + +// The Instance class. One of these exists for each instance of your NaCl +// module on the web page. The browser will ask the Module object to create +// a new Instance for each occurrence of the <embed> tag that has these +// attributes: +// type="application/x-nacl" +// nacl="pi_generator.nmf" +class PiGenerator : public pp::Instance { + public: + explicit PiGenerator(PP_Instance instance); + virtual ~PiGenerator(); + + // Start up the ComputePi() thread. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Update the graphics context to the new size, and regenerate |pixel_buffer_| + // to fit the new size as well. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip); + + // Called by the browser to handle the postMessage() call in Javascript. + // The message in this case is expected to contain the string 'paint', and + // if so this invokes the Paint() function. If |var_message| is not a string + // type, or contains something other than 'paint', this method posts an + // invalid value for Pi (-1.0) back to the browser. + virtual void HandleMessage(const pp::Var& var_message); + + // Return a pointer to the pixels represented by |pixel_buffer_|. When this + // method returns, the underlying |pixel_buffer_| object is locked. This + // call must have a matching UnlockPixels() or various threading errors + // (e.g. deadlock) will occur. + uint32_t* LockPixels(); + // Release the image lock acquired by LockPixels(). + void UnlockPixels() const; + + // Flushes its contents of |pixel_buffer_| to the 2D graphics context. The + // ComputePi() thread fills in |pixel_buffer_| pixels as it computes Pi. + // This method is called by HandleMessage when a message containing 'paint' + // is received. Echos the current value of pi as computed by the Monte Carlo + // method by posting the value back to the browser. + void Paint(); + + bool quit() const { + return quit_; + } + + // |pi_| is computed in the ComputePi() thread. + double pi() const { + return pi_; + } + + int width() const { + return pixel_buffer_ ? pixel_buffer_->size().width() : 0; + } + int height() const { + return pixel_buffer_ ? pixel_buffer_->size().height() : 0; + } + + // Indicate whether a flush is pending. This can only be called from the + // main thread; it is not thread safe. + bool flush_pending() const { + return flush_pending_; + } + void set_flush_pending(bool flag) { + flush_pending_ = flag; + } + + private: + // Create and initialize the 2D context used for drawing. + void CreateContext(const pp::Size& size); + // Destroy the 2D drawing context. + void DestroyContext(); + // Push the pixels to the browser, then attempt to flush the 2D context. If + // there is a pending flush on the 2D context, then update the pixels only + // and do not flush. + void FlushPixelBuffer(); + + bool IsContextValid() const { + return graphics_2d_context_ != NULL; + } + + mutable pthread_mutex_t pixel_buffer_mutex_; + pp::Graphics2D* graphics_2d_context_; + pp::ImageData* pixel_buffer_; + bool flush_pending_; + bool quit_; + pthread_t compute_pi_thread_; + double pi_; + + // ComputePi() estimates Pi using Monte Carlo method and it is executed by a + // separate thread created in SetWindow(). ComputePi() puts kMaxPointCount + // points inside the square whose length of each side is 1.0, and calculates + // the ratio of the number of points put inside the inscribed quadrant divided + // by the total number of random points to get Pi/4. + static void* ComputePi(void* param); +}; + +} // namespace pi_generator + +#endif // EXAMPLES_PI_GENERATOR_PI_GENERATOR_H_ + diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator.html b/native_client_sdk/src/examples/pi_generator/pi_generator.html new file mode 100644 index 0000000..f63e73d --- /dev/null +++ b/native_client_sdk/src/examples/pi_generator/pi_generator.html @@ -0,0 +1,83 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + <head> + <title>Monte Carlo Estimate for Pi</title> + <script type="text/javascript"> + var piGenerator = null; + var paintInterval = null; + + // Start up the paint timer when the NaCl module has loaded. + function moduleDidLoad() { + piGenerator = document.getElementById('piGenerator'); + paintInterval = setInterval('piGenerator.postMessage("paint")', 5); + } + + // Handle a message coming from the NaCl module. The message payload is + // assumed to contain the current estimated value of Pi. Update the Pi + // text display with this value. + function handleMessage(message_event) { + document.form.pi.value = message_event.data; + } + + function pageDidUnload() { + clearInterval(paintInterval); + } + </script> + </head> + <body id="bodyId" onunload="pageDidUnload()"> + <h1>Monte Carlo Estimate for Pi</h1> + <p> + The Native Client module executed in this page creates a thread + that estimates pi (π) using the Monte Carlo method. + The thread randomly puts 1,000,000,000 points + inside a square that shares two sides with a quarter circle (a quadrant). + Because the area of + the quadrant is r²π/4 + and the area of + the square is r², + dividing the number of points inside the quadrant + by the number of points inside the square gives us + an estimate of π/4. + The textbox under the square + shows the current estimate of π. + </p> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the + runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl + module. To load the debug versions of your .nexes, set the 'src' + attribute to the _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="piGenerator" + width=200 height=200 + src="pi_generator.nmf" + type="application/x-nacl" /> + </div> + <br /> + <form name="form"> + <input type="text" size="15" name="pi" /> + </form> + </body> +</html> diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator_dbg.html b/native_client_sdk/src/examples/pi_generator/pi_generator_dbg.html new file mode 100644 index 0000000..3308b15 --- /dev/null +++ b/native_client_sdk/src/examples/pi_generator/pi_generator_dbg.html @@ -0,0 +1,83 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+ <head>
+ <title>Monte Carlo Estimate for Pi</title>
+ <script type="text/javascript">
+ var piGenerator = null;
+ var paintInterval = null;
+
+ // Start up the paint timer when the NaCl module has loaded.
+ function moduleDidLoad() {
+ piGenerator = document.getElementById('piGenerator');
+ paintInterval = setInterval('piGenerator.postMessage("paint")', 5);
+ }
+
+ // Handle a message coming from the NaCl module. The message payload is
+ // assumed to contain the current estimated value of Pi. Update the Pi
+ // text display with this value.
+ function handleMessage(message_event) {
+ document.form.pi.value = message_event.data;
+ }
+
+ function pageDidUnload() {
+ clearInterval(paintInterval);
+ }
+ </script>
+ </head>
+ <body id="bodyId" onunload="pageDidUnload()">
+ <h1>Monte Carlo Estimate for Pi</h1>
+ <p>
+ The Native Client module executed in this page creates a thread
+ that estimates pi (π) using the Monte Carlo method.
+ The thread randomly puts 1,000,000,000 points
+ inside a square that shares two sides with a quarter circle (a quadrant).
+ Because the area of
+ the quadrant is r²π/4
+ and the area of
+ the square is r²,
+ dividing the number of points inside the quadrant
+ by the number of points inside the square gives us
+ an estimate of π/4.
+ The textbox under the square
+ shows the current estimate of π.
+ </p>
+ <!-- Load the published .nexe. This includes the 'src' attribute which
+ shows how to load multi-architecture modules. Each entry in the "nexes"
+ object in the .nmf manifest file is a key-value pair: the key is the
+ runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl
+ module. To load the debug versions of your .nexes, set the 'src'
+ attribute to the _dbg.nmf version of the manifest file.
+
+ Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
+ and a 'message' event listener attached. This wrapping method is used
+ instead of attaching the event listeners directly to the <EMBED> element to
+ ensure that the listeners are active before the NaCl module 'load' event
+ fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
+ pp::Instance.PostMessage() (in C++) from within the initialization code in
+ your NaCl module.
+ -->
+ <div id="listener">
+ <script type="text/javascript">
+ var listener = document.getElementById('listener')
+ listener.addEventListener('load', moduleDidLoad, true);
+ listener.addEventListener('message', handleMessage, true);
+ </script>
+
+ <embed name="nacl_module"
+ id="piGenerator"
+ width=200 height=200
+ src="pi_generator_dbg.nmf"
+ type="application/x-nacl" />
+ </div>
+ <br />
+ <form name="form">
+ <input type="text" size="15" name="pi" />
+ </form>
+ </body>
+</html>
diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator_module.cc b/native_client_sdk/src/examples/pi_generator/pi_generator_module.cc new file mode 100644 index 0000000..8477fbf --- /dev/null +++ b/native_client_sdk/src/examples/pi_generator/pi_generator_module.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <ppapi/cpp/module.h> + +#include "examples/pi_generator/pi_generator.h" + +namespace pi_generator { +// The Module class. The browser calls the CreateInstance() method to create +// an instance of your NaCl module on the web page. The browser creates a new +// instance for each <embed> tag with type="application/x-nacl". +class PiGeneratorModule : public pp::Module { + public: + PiGeneratorModule() : pp::Module() {} + virtual ~PiGeneratorModule() {} + + // Create and return a PiGeneratorInstance object. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new PiGenerator(instance); + } +}; +} // namespace pi_generator + +// Factory function called by the browser when the module is first loaded. +// The browser keeps a singleton of this module. It calls the +// CreateInstance() method on the object you return to make instances. There +// is one instance per <embed> tag on the page. This is the main binding +// point for your NaCl module with the browser. +namespace pp { +Module* CreateModule() { + return new pi_generator::PiGeneratorModule(); +} +} // namespace pp diff --git a/native_client_sdk/src/examples/pong/build.scons b/native_client_sdk/src/examples/pong/build.scons new file mode 100644 index 0000000..a438bb0 --- /dev/null +++ b/native_client_sdk/src/examples/pong/build.scons @@ -0,0 +1,40 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='pong', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['pong.cc', 'pong_module.cc', 'view.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'pong') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('pong') + +app_files = [ + 'pong.html', + 'pong.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/pong/pong.cc b/native_client_sdk/src/examples/pong/pong.cc new file mode 100644 index 0000000..75ffc7c --- /dev/null +++ b/native_client_sdk/src/examples/pong/pong.cc @@ -0,0 +1,414 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/pong/pong.h" + +#include <stdio.h> +#include <cmath> +#include <string> +#include "examples/pong/view.h" +#include "ppapi/c/pp_file_info.h" +#include "ppapi/c/ppb_file_io.h" +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/file_io.h" +#include "ppapi/cpp/file_ref.h" +#include "ppapi/cpp/file_system.h" +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/var.h" + +namespace { + +const uint32_t kSpaceBar = 0x20; +const uint32_t kUpArrow = 0x26; +const uint32_t kDownArrow = 0x28; + +const int32_t kMaxPointsAllowed = 256; +const std::string kResetScoreMethodId = "resetScore"; +const uint32_t kUpdateDistance = 4; +const int32_t kUpdateInterval = 17; // milliseconds + +} // namespace + +namespace pong { + +// Callbacks that are called asynchronously by the system as a result of various +// pp::FileIO methods. +namespace AsyncCallbacks { +// Callback that is called as a result of pp::FileIO::Flush +void FlushCallback(void*, int32_t) { +} + +// Callback that is called as a result of pp::FileIO::Write +void WriteCallback(void* data, int32_t bytes_written) { + if (bytes_written < 0) + return; // error + Pong* pong = static_cast<Pong*>(data); + pong->offset_ += bytes_written; + if (pong->offset_ == pong->bytes_buffer_.length()) { + pong->file_io_->Flush(pp::CompletionCallback(FlushCallback, NULL)); + } else { + // Not all the bytes to be written have been written, so call + // pp::FileIO::Write again. + pong->file_io_->Write(pong->offset_, &pong->bytes_buffer_[pong->offset_], + pong->bytes_buffer_.length() - pong->offset_, + pp::CompletionCallback(WriteCallback, pong)); + } +} + +// Callback that is called as a result of pp::FileSystem::Open +void FileSystemOpenCallback(void* data, int32_t result) { + if (result != PP_OK) + return; + Pong* pong = static_cast<Pong*>(data); + pong->UpdateScoreFromFile(); +} + +// Callback that is called as a result of pp::FileIO::Read +void ReadCallback(void* data, int32_t bytes_read) { + if (bytes_read < 0) + return; // error + Pong* pong = static_cast<Pong*>(data); + pong->bytes_to_read_ -= bytes_read; + if (pong->bytes_to_read_ == 0) { + // File has been read to completion. Parse the bytes to get the scores. + pong->UpdateScoreFromBuffer(); + } else { + pong->offset_ += bytes_read; + pong->file_io_->Read(pong->offset_, + &pong->bytes_buffer_[pong->offset_], + pong->bytes_to_read_, + pp::CompletionCallback(ReadCallback, pong)); + } +} + +// Callback that is called as a result of pp::FileIO::Query +void QueryCallback(void* data, int32_t result) { + if (result != PP_OK) + return; + Pong* pong = static_cast<Pong*>(data); + pong->bytes_to_read_ = pong->file_info_.size; + pong->offset_ = 0; + pong->bytes_buffer_.resize(pong->bytes_to_read_); + pong->file_io_->Read(pong->offset_, + &pong->bytes_buffer_[0], + pong->bytes_to_read_, + pp::CompletionCallback(ReadCallback, pong)); +} + +// Callback that is called as a result of pp::FileIO::Open +void FileOpenCallback(void*data, int32_t result) { + if (result != PP_OK) { + return; + } + Pong* pong = static_cast<Pong*>(data); + // Query the file in order to get the file size. + pong->file_io_->Query(&pong->file_info_, pp::CompletionCallback(QueryCallback, + pong)); +} + +// Callback that is called as a result of pp::Core::CallOnMainThread +void UpdateCallback(void* data, int32_t /*result*/) { + Pong* pong = static_cast<Pong*>(data); + pong->Update(); +} + +} // namespace AsyncCallbacks + +class UpdateScheduler { + public: + UpdateScheduler(int32_t delay, Pong* pong) + : delay_(delay), pong_(pong) {} + ~UpdateScheduler() { + pp::Core* core = pp::Module::Get()->core(); + core->CallOnMainThread(delay_, pp::CompletionCallback( + AsyncCallbacks::UpdateCallback, pong_)); + } + + private: + int32_t delay_; // milliseconds + Pong* pong_; // weak +}; + +Pong::Pong(PP_Instance instance) + : pp::Instance(instance), + bytes_to_read_(0), + offset_(0), + file_io_(NULL), + file_ref_(NULL), + file_system_(NULL), + view_(NULL), + delta_x_(0), + delta_y_(0), + player_score_(0), + computer_score_(0) { + // Request to receive input events. + RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_KEYBOARD); +} + +Pong::~Pong() { + delete view_; + file_io_->Close(); + delete file_io_; + delete file_ref_; + delete file_system_; +} + +bool Pong::Init(uint32_t argc, const char* argn[], const char* argv[]) { + view_ = new View(this); + // Read the score from file. + file_system_ = new pp::FileSystem(this, PP_FILESYSTEMTYPE_LOCALPERSISTENT); + file_ref_ = new pp::FileRef(*file_system_, "/pong_score"); + // We kick off a series of callbacks which open a file, query the file for + // it's length in bytes, read the file contents, and then update the score + // display based on the file contents. + int32_t rv = file_system_->Open( + 1024, pp::CompletionCallback(AsyncCallbacks::FileSystemOpenCallback, + this)); + if (rv != PP_OK_COMPLETIONPENDING) { + PostMessage(pp::Var("ERROR: Could not open local persistent file system.")); + return true; + } + UpdateScoreDisplay(); + UpdateScheduler(kUpdateInterval, this); + return true; +} + +void Pong::DidChangeView(const pp::Rect& position, + const pp::Rect& clip) { + pp::Size view_size = view_->GetSize(); + const bool view_was_empty = view_size.IsEmpty(); + view_->UpdateView(position, clip, this); + if (view_was_empty) + ResetPositions(); +} + +bool Pong::HandleInputEvent(const pp::InputEvent& event) { + if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEUP) { + // By notifying the browser mouse clicks are handled, the application window + // is able to get focus and receive key events. + return true; + } else if (event.GetType() == PP_INPUTEVENT_TYPE_KEYUP) { + pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event); + return view_->KeyUp(key); + } else if (event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN) { + pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event); + return view_->KeyDown(key); + } + return false; +} + + +void Pong::HandleMessage(const pp::Var& var_message) { + if (!var_message.is_string()) + return; + std::string message = var_message.AsString(); + if (message == kResetScoreMethodId) { + ResetScore(); + } +} + +void Pong::Update() { + // Schedule another update + UpdateScheduler(kUpdateInterval, this); + + const uint32_t key_code = view_->last_key_code(); + if (key_code == kSpaceBar) { + ResetPositions(); + return; + } + if (ball_.right() < court_.x()) { + ++computer_score_; + if (computer_score_ > kMaxPointsAllowed) { + ResetScore(); + } else { + WriteScoreToFile(); + UpdateScoreDisplay(); + } + ResetPositions(); + return; + } + if (ball_.x() > court_.right()) { + ++player_score_; + if (player_score_ > kMaxPointsAllowed) { + ResetScore(); + } else { + WriteScoreToFile(); + UpdateScoreDisplay(); + } + ResetPositions(); + return; + } + // Update human controlled paddle + if (key_code == kUpArrow) { + left_paddle_.Offset(0, -kUpdateDistance); + if (left_paddle_.y() - 1 < court_.y()) { + left_paddle_.Offset(0, court_.y() - left_paddle_.y() + 1); + } + } else if (key_code == kDownArrow) { + left_paddle_.Offset(0, kUpdateDistance); + if (left_paddle_.bottom() + 1 > court_.bottom()) { + left_paddle_.Offset(0, court_.bottom() - left_paddle_.bottom() - 1); + } + } + + // Update AI controlled paddle + BallDirection direction = RightPaddleNextMove(); + if (direction == kUpDirection) { + right_paddle_.Offset(0, -kUpdateDistance); + if (right_paddle_.y() < court_.y() + 1) { + right_paddle_.Offset(0, court_.y() - right_paddle_.y() + 1); + } + } else if (direction == kDownDirection) { + right_paddle_.Offset(0, kUpdateDistance); + if (right_paddle_.bottom() > court_.bottom() - 1) { + right_paddle_.Offset(0, court_.bottom() - right_paddle_.bottom() - 1); + } + } + + // Bounce ball off bottom of screen + if (ball_.bottom() >= court_.bottom() - 1) { + ball_.Offset(0, court_.bottom() - ball_.bottom() - 1); + delta_y_ = -delta_y_; + } + // Bounce ball off top of screen + if (ball_.y() <= court_.y() + 1) { + ball_.Offset(0, court_.y() - ball_.y() + 1); + delta_y_ = -delta_y_; + } + // Bounce ball off human controlled paddle + if (left_paddle_.Intersects(ball_)) { + delta_x_ = abs(delta_x_); + if (ball_.CenterPoint().y() < + left_paddle_.y() + left_paddle_.height() / 5) { + delta_y_ -= kUpdateDistance; + if (delta_y_ == 0) + delta_y_ = -kUpdateDistance; + } else if (ball_.CenterPoint().y() > + left_paddle_.bottom() - left_paddle_.height() / 5) { + delta_y_ += kUpdateDistance; + if (delta_y_ == 0) + delta_y_ = kUpdateDistance; + } + } + // Bounce ball off ai controlled paddle + if (right_paddle_.Intersects(ball_)) { + delta_x_ = -abs(delta_x_); + if (ball_.CenterPoint().y() > + right_paddle_.bottom() - right_paddle_.height() / 5) { + delta_y_ += kUpdateDistance; + if (delta_y_ == 0) + delta_y_ = kUpdateDistance; + } else if (ball_.CenterPoint().y() < + right_paddle_.y() + right_paddle_.height() / 5) { + delta_y_ -= kUpdateDistance; + if (delta_y_ == 0) + delta_y_ -= kUpdateDistance; + } + } + + // Move ball + ball_.Offset(delta_x_, delta_y_); + + view_->set_ball_rect(ball_); + view_->set_left_paddle_rect(left_paddle_); + view_->set_right_paddle_rect(right_paddle_); + view_->Draw(); +} + +void Pong::UpdateScoreFromBuffer() { + size_t pos = bytes_buffer_.find_first_of(':'); + player_score_ = ::atoi(bytes_buffer_.substr(0, pos).c_str()); + computer_score_ = ::atoi(bytes_buffer_.substr(pos + 1).c_str()); + UpdateScoreDisplay(); +} + +void Pong::UpdateScoreFromFile() { + file_io_ = new pp::FileIO(this); + file_io_->Open(*file_ref_, + PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE | + PP_FILEOPENFLAG_CREATE, + pp::CompletionCallback(AsyncCallbacks::FileOpenCallback, + this)); +} + +void Pong::WriteScoreToFile() { + if (file_io_ == NULL) + return; + // Write the score in <player score>:<computer score> format. + size_t score_string_length = 1 + (player_score_ ? log(player_score_) : 1) + 1 + + (computer_score_ ? log(computer_score_) : 1) + 1; + bytes_buffer_.resize(score_string_length); + snprintf(&bytes_buffer_[0], bytes_buffer_.length(), "%i:%i", player_score_, + computer_score_); + offset_ = 0; // overwrite score in file. + file_io_->Write(offset_, + bytes_buffer_.c_str(), + bytes_buffer_.length(), + pp::CompletionCallback(AsyncCallbacks::WriteCallback, this)); +} + +void Pong::ResetPositions() { + pp::Size court_size = view_->GetSize(); + pp::Rect court_rect(court_size); + court_.SetRect(court_rect); + + pp::Rect rect; + rect.set_width(20); + rect.set_height(20); + rect.set_x((court_rect.x() + court_rect.width()) / 2 - rect.width() / 2); + rect.set_y(court_rect.y() + court_rect.height() - rect.height()); + ball_.SetRect(rect); + + const float paddle_width = 10; + const float paddle_height = 99; + const float paddle_pos_y = + (court_rect.y() + court_rect.height()) / 2 - rect.height() / 2; + rect.set_width(paddle_width); + rect.set_height(paddle_height); + rect.set_x(court_rect.x() + court_rect.width() / 5 + 1); + rect.set_y(paddle_pos_y); + left_paddle_.SetRect(rect); + + rect.set_width(paddle_width); + rect.set_height(paddle_height); + rect.set_x(court_rect.x() + 4 * court_rect.width() / 5 - rect.width() - 1); + rect.set_y(paddle_pos_y); + right_paddle_.SetRect(rect); + + delta_x_ = delta_y_ = kUpdateDistance; +} + +void Pong::ResetScore() { + player_score_ = 0; + computer_score_ = 0; + UpdateScoreDisplay(); +} + +Pong::BallDirection Pong::RightPaddleNextMove() const { + static int32_t last_ball_y = 0; + int32_t ball_y = ball_.CenterPoint().y(); + BallDirection ball_direction = + ball_y < last_ball_y ? kUpDirection : kDownDirection; + last_ball_y = ball_y; + + if (ball_y < right_paddle_.y()) + return kUpDirection; + if (ball_y > right_paddle_.bottom()) + return kDownDirection; + return ball_direction; +} + +void Pong::UpdateScoreDisplay() { + if (file_io_ == NULL) + return; // Since we cant't save the score to file, do nothing and return. + size_t message_length = 18 + (player_score_ ? log(player_score_) : 1) + 1 + + (computer_score_ ? log(computer_score_) : 1) + 1; + std::string score_message(message_length, '\0'); + snprintf(&score_message[0], score_message.length(), + "You: %i Computer: %i", player_score_, computer_score_); + PostMessage(pp::Var(score_message)); +} + +} // namespace pong diff --git a/native_client_sdk/src/examples/pong/pong.h b/native_client_sdk/src/examples/pong/pong.h new file mode 100644 index 0000000..8f38e3e --- /dev/null +++ b/native_client_sdk/src/examples/pong/pong.h @@ -0,0 +1,92 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_PONG_PONG_H_ +#define EXAMPLES_PONG_PONG_H_ + +#include <string> + +#include "ppapi/c/pp_file_info.h" +#include "ppapi/cpp/graphics_2d.h" +#include "ppapi/cpp/image_data.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/size.h" + +namespace pp { +class FileIO; +class FileRef; +class FileSystem; +class Rect; +} // namespace pp + +namespace pong { + +class View; + +// The Instance class. One of these exists for each instance of your NaCl +// module on the web page. The browser will ask the Module object to create +// a new Instance for each occurrence of the <embed> tag that has these +// attributes: +// type="application/x-nacl" +// nacl="pong.nmf" +class Pong : public pp::Instance { + public: + explicit Pong(PP_Instance instance); + virtual ~Pong(); + + // Open the file (if available) that stores the game scores. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Update the graphics context to the new size, and regenerate |pixel_buffer_| + // to fit the new size as well. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip); + + virtual bool HandleInputEvent(const pp::InputEvent& event); + + // Called by the browser to handle the postMessage() call in Javascript. + // The message in this case is expected to contain the string 'update', or + // 'resetScore' in order to invoke either the Update or ResetScore function + // respectively. + virtual void HandleMessage(const pp::Var& var_message); + + void Update(); + void UpdateScoreFromBuffer(); + void UpdateScoreFromFile(); + void WriteScoreToFile(); + + PP_FileInfo file_info_; + int32_t bytes_to_read_; + int64_t offset_; + pp::FileIO* file_io_; + pp::FileRef* file_ref_; + pp::FileSystem* file_system_; + std::string bytes_buffer_; + + private: + Pong(const Pong&); // Disallow copy + + enum BallDirection { + kUpDirection = 0, + kDownDirection + }; + void ResetPositions(); + void ResetScore(); + BallDirection RightPaddleNextMove() const; + void UpdateScoreDisplay(); + + View* view_; + pp::Rect left_paddle_; + pp::Rect right_paddle_; + pp::Rect ball_; + pp::Rect court_; + int32_t delta_x_; + int32_t delta_y_; + int player_score_; + int computer_score_; +}; + +} // namespace pong + +#endif // EXAMPLES_PONG_PONG_H_ diff --git a/native_client_sdk/src/examples/pong/pong.html b/native_client_sdk/src/examples/pong/pong.html new file mode 100644 index 0000000..59a5915 --- /dev/null +++ b/native_client_sdk/src/examples/pong/pong.html @@ -0,0 +1,74 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + <head> + <title>Pong</title> + <script type="text/javascript"> + var pong = null; + var paintInterval = null; + + // Handle a message coming from the NaCl module. The message payload is + // assumed to contain the current game score. Update the score text + // display with this value. + // Note that errors are also sent to this handler. A message starting + // with 'ERROR' is considered an error, all other strings are assumed + // to be scores. + function handleMessage(message_event) { + if (message_event.data.indexOf('ERROR') == 0) { + document.getElementById('error_log').innerHTML = message_event.data; + } else { + document.getElementById('score').innerHTML = message_event.data; + } + } + + function pageDidUnload() { + clearInterval(paintInterval); + } + function resetScore() { + pong = document.getElementById('pong'); + pong.postMessage("resetScore"); + } + </script> + </head> + <body id="bodyId" onunload="pageDidUnload()"> + <h1>Pong</h1> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the + runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl + module. To load the debug versions of your .nexes, set the 'src' + attribute to the _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + window.webkitStorageInfo.requestQuota(PERSISTENT, 1024); + var listener = document.getElementById('listener') + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="pong" + width=800 height=600 + src="pong.nmf" + type="application/x-nacl" /> + </div> + <br /> + <p id="score"> + </p> + <button onclick="resetScore()">Reset score</button> + <p id="error_log"></p> + </body> +</html> diff --git a/native_client_sdk/src/examples/pong/pong_module.cc b/native_client_sdk/src/examples/pong/pong_module.cc new file mode 100644 index 0000000..c74ea28 --- /dev/null +++ b/native_client_sdk/src/examples/pong/pong_module.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <ppapi/cpp/module.h> + +#include "examples/pong/pong.h" + +namespace pong { +// The Module class. The browser calls the CreateInstance() method to create +// an instance of your NaCl module on the web page. The browser creates a new +// instance for each <embed> tag with type="application/x-nacl". +class PongModule : public pp::Module { + public: + PongModule() : pp::Module() {} + virtual ~PongModule() {} + + // Create and return a PiGeneratorInstance object. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new Pong(instance); + } +}; +} // namespace pong + +// Factory function called by the browser when the module is first loaded. +// The browser keeps a singleton of this module. It calls the +// CreateInstance() method on the object you return to make instances. There +// is one instance per <embed> tag on the page. This is the main binding +// point for your NaCl module with the browser. +namespace pp { +Module* CreateModule() { + return new pong::PongModule(); +} +} // namespace pp diff --git a/native_client_sdk/src/examples/pong/view.cc b/native_client_sdk/src/examples/pong/view.cc new file mode 100644 index 0000000..4bb11b9 --- /dev/null +++ b/native_client_sdk/src/examples/pong/view.cc @@ -0,0 +1,162 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/pong/view.h" + +#include <math.h> +#include <stdio.h> +#include <string.h> + +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/graphics_2d.h" +#include "ppapi/cpp/image_data.h" +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/point.h" +#include "ppapi/cpp/var.h" + +// Input event key codes. PPAPI uses Windows Virtual key codes. +const uint32_t kSpaceBar = 0x20; +const uint32_t kUpArrow = 0x26; +const uint32_t kDownArrow = 0x28; + +namespace { + +const uint32_t kOpaqueColorMask = 0xff000000; // Opaque pixels. +const uint32_t kWhiteMask = 0xffffff; + +// This is called by the browser when the 2D context has been flushed to the +// browser window. +void FlushCallback(void* data, int32_t result) { + static_cast<pong::View*>(data)->set_flush_pending(false); +} + +} // namespace + +namespace pong { +View::View(pp::Instance* instance) + : instance_(instance), last_key_code_(0x0), flush_pending_(false), + graphics_2d_context_(NULL), pixel_buffer_(NULL) {} + +View::~View() { + DestroyContext(); + delete pixel_buffer_; +} + +pp::Size View::GetSize() const { + pp::Size size; + if (graphics_2d_context_) { + size.SetSize(graphics_2d_context_->size().width(), + graphics_2d_context_->size().height()); + } + return size; +} + +bool View::KeyDown(const pp::KeyboardInputEvent& key) { + last_key_code_ = key.GetKeyCode(); + if (last_key_code_ == kSpaceBar || last_key_code_ == kUpArrow || + last_key_code_ == kDownArrow) + return true; + return false; +} + +bool View::KeyUp(const pp::KeyboardInputEvent& key) { + if (last_key_code_ == key.GetKeyCode()) { + last_key_code_ = 0x0; // Indicates key code is not set. + } + return false; +} + +void View::Draw() { + uint32_t* pixels = static_cast<uint32_t*>(pixel_buffer_->data()); + if (NULL == pixels) + return; + // Clear the buffer + const int32_t height = pixel_buffer_->size().height(); + const int32_t width = pixel_buffer_->size().width(); + for (int32_t py = 0; py < height; ++py) { + for (int32_t px = 0; px < width; ++px) { + const int32_t pos = px + py * width; + uint32_t color = kOpaqueColorMask; + // Draw the paddles + if (left_paddle_rect_.Contains(px, py) || + right_paddle_rect_.Contains(px, py)) { + color |= kWhiteMask; + } else { + pp::Point center_point = ball_rect_.CenterPoint(); + float radius = ball_rect_.width() / 2; + float distance_x = px - center_point.x(); + float distance_y = py - center_point.y(); + float distance = + sqrt(distance_x * distance_x + distance_y * distance_y); + // Draw the ball + if (distance <= radius) + color |= kWhiteMask; + } + pixels[pos] = color; + } + } + + FlushPixelBuffer(); +} + +void View::UpdateView(const pp::Rect& position, + const pp::Rect& clip, + pp::Instance* instance) { + const int32_t width = + pixel_buffer_ ? pixel_buffer_->size().width() : 0; + const int32_t height = + pixel_buffer_ ? pixel_buffer_->size().height() : 0; + + if (position.size().width() == width && + position.size().height() == height) + return; // Size didn't change, no need to update anything. + + // Create a new device context with the new size. + DestroyContext(); + CreateContext(position.size(), instance); + // Delete the old pixel buffer and create a new one. + delete pixel_buffer_; + pixel_buffer_ = NULL; + if (graphics_2d_context_ != NULL) { + pixel_buffer_ = new pp::ImageData(instance, PP_IMAGEDATAFORMAT_BGRA_PREMUL, + graphics_2d_context_->size(), + false); + } +} + +void View::CreateContext(const pp::Size& size, pp::Instance* instance) { + if (IsContextValid()) + return; + graphics_2d_context_ = new pp::Graphics2D(instance, size, + false); + if (!instance->BindGraphics(*graphics_2d_context_)) { + instance_->PostMessage(pp::Var("ERROR: Couldn't bind the device context")); + } +} + +void View::DestroyContext() { + if (!IsContextValid()) + return; + delete graphics_2d_context_; + graphics_2d_context_ = NULL; +} + +void View::FlushPixelBuffer() { + if (!IsContextValid()) + return; + // Note that the pixel lock is held while the buffer is copied into the + // device context and then flushed. + graphics_2d_context_->PaintImageData(*pixel_buffer_, pp::Point()); + if (flush_pending_) + return; + flush_pending_ = true; + graphics_2d_context_->Flush(pp::CompletionCallback(&FlushCallback, this)); +} + +bool View::IsContextValid() const { + return graphics_2d_context_ != NULL; +} + +} // namespace pong diff --git a/native_client_sdk/src/examples/pong/view.h b/native_client_sdk/src/examples/pong/view.h new file mode 100644 index 0000000..269ff99 --- /dev/null +++ b/native_client_sdk/src/examples/pong/view.h @@ -0,0 +1,75 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_PONG_VIEW_H_ +#define EXAMPLES_PONG_VIEW_H_ + +#include "ppapi/cpp/rect.h" + +namespace pp { +class Graphics2D; +class ImageData; +class Instance; +class KeyboardInputEvent; +class Rect; +class Size; +} // namespace pp + +namespace pong { + +class View { + public: + explicit View(pp::Instance* instance); + ~View(); + + const uint32_t& last_key_code() const { + return last_key_code_; + } + void set_left_paddle_rect(const pp::Rect& left_paddle_rect) { + left_paddle_rect_ = left_paddle_rect; + } + void set_right_paddle_rect(const pp::Rect& right_paddle_rect) { + right_paddle_rect_ = right_paddle_rect; + } + void set_ball_rect(const pp::Rect& ball_rect) { + ball_rect_ = ball_rect; + } + void set_flush_pending(bool flush_pending) { + flush_pending_ = flush_pending; + } + pp::Size GetSize() const; + bool KeyDown(const pp::KeyboardInputEvent& key); + bool KeyUp(const pp::KeyboardInputEvent& key); + void Draw(); + void UpdateView(const pp::Rect& position, + const pp::Rect& clip, + pp::Instance* instance); + + private: + pp::Instance* const instance_; // weak + // Create and initialize the 2D context used for drawing. + void CreateContext(const pp::Size& size, pp::Instance* instance); + // Destroy the 2D drawing context. + void DestroyContext(); + // Push the pixels to the browser, then attempt to flush the 2D context. If + // there is a pending flush on the 2D context, then update the pixels only + // and do not flush. + void FlushPixelBuffer(); + bool IsContextValid() const; + + uint32_t last_key_code_; + // Geometry for drawing + pp::Rect left_paddle_rect_; + pp::Rect right_paddle_rect_; + pp::Rect ball_rect_; + // Drawing stuff + bool flush_pending_; + pp::Graphics2D* graphics_2d_context_; + pp::ImageData* pixel_buffer_; +}; + + +} // namespace pong + +#endif // EXAMPLES_PONG_VIEW_H_ diff --git a/native_client_sdk/src/examples/scons b/native_client_sdk/src/examples/scons new file mode 100755 index 0000000..7742fd7 --- /dev/null +++ b/native_client_sdk/src/examples/scons @@ -0,0 +1,39 @@ +#!/bin/bash +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +readonly SCRIPT_DIR="$(dirname "$0")" +readonly SCRIPT_DIR_ABS="$(cd "${SCRIPT_DIR}" ; pwd -P)" + +# NACL_SDK_ROOT must be set. +if [ x"${NACL_SDK_ROOT}"x == "xx" ] ; then + echo "Error: NACL_SDK_ROOT is not set." + exit 1; +fi + +# NACL_TARGET_PLATFORM is really the name of a folder with the base dir - +# usually NACL_SDK_ROOT - within which the toolchain for the target platform +# are found. +# Replace the platform with the name of your target platform. For example, to +# build applications that target the pepper_17 API, set +# NACL_TARGET_PLATFORM="pepper_17" +if [ x"${NACL_TARGET_PLATFORM}"x == "xx" ] ; then + export NACL_TARGET_PLATFORM="pepper_17" +fi + +readonly NACL_PLATFORM_DIR="${NACL_SDK_ROOT}/${NACL_TARGET_PLATFORM}" +readonly BASE_SCRIPT="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/script/scons" + +export SCONS_LIB_DIR="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/engine" +export PYTHONPATH="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/engine" +# We have to do this because scons overrides PYTHONPATH and does not preserve +# what is provided by the OS. The custom variable name won't be overwritten. +export PYMOX="${NACL_PLATFORM_DIR}/third_party/pymox" + +"${BASE_SCRIPT}" --file=build.scons \ + --site-dir="${NACL_PLATFORM_DIR}/build_tools/nacl_sdk_scons" \ + $* + diff --git a/native_client_sdk/src/examples/scons.bat b/native_client_sdk/src/examples/scons.bat new file mode 100755 index 0000000..2a67758 --- /dev/null +++ b/native_client_sdk/src/examples/scons.bat @@ -0,0 +1,43 @@ +@echo off + +:: Copyright (c) 2011 The Native Client Authors. All rights reserved. +:: Use of this source code is governed by a BSD-style license that can be +:: found in the LICENSE file. + +setlocal + +:: NACL_SDK_ROOT must be set. +if not defined NACL_SDK_ROOT ( + echo Error: NACL_SDK_ROOT is not set. + echo Please set NACL_SDK_ROOT to the full path of the Native Client SDK. + echo For example: + echo set NACL_SDK_ROOT=D:\nacl_sdk + goto end +) + +:: NACL_TARGET_PLATFORM is really the name of a folder with the base dir - +:: usually NACL_SDK_ROOT - within which the toolchain for the target platform +:: are found. +:: Replace the platform with the name of your target platform. For example, to +:: build applications that target the pepper_17 API, set +:: NACL_TARGET_PLATFORM=pepper_17 +if not defined NACL_TARGET_PLATFORM ( + set NACL_TARGET_PLATFORM=pepper_17 +) + +set NACL_PLATFORM_DIR=%NACL_SDK_ROOT%\%NACL_TARGET_PLATFORM% + +set SCONS_LIB_DIR=%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\engine +set PYTHONPATH=%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\engine + +:: We have to do this because scons overrides PYTHONPATH and does not preserve +:: what is provided by the OS. The custom variable name won't be overwritten. +set PYMOX=%NACL_PLATFORM_DIR%\third_party\pymox + +:: Run the included copy of scons. +python -O -OO "%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\script\scons" ^ +--warn no-visual-c-missing ^ +--file=build.scons ^ +--site-dir="%NACL_PLATFORM_DIR%\build_tools\nacl_sdk_scons" %* + +:end diff --git a/native_client_sdk/src/examples/sine_synth/build.scons b/native_client_sdk/src/examples/sine_synth/build.scons new file mode 100644 index 0000000..5b10dc8 --- /dev/null +++ b/native_client_sdk/src/examples/sine_synth/build.scons @@ -0,0 +1,40 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='sine_synth', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + ) + +sources = ['sine_synth.cc'] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'sine_synth') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('sine_synth') + +app_files = [ + 'sine_synth.html', + 'sine_synth.nmf', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/sine_synth/sine_synth.cc b/native_client_sdk/src/examples/sine_synth/sine_synth.cc new file mode 100644 index 0000000..2305020 --- /dev/null +++ b/native_client_sdk/src/examples/sine_synth/sine_synth.cc @@ -0,0 +1,183 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <cassert> +#include <cmath> +#include <limits> +#include <sstream> +#include "ppapi/cpp/audio.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" + +namespace { +const char* const kPlaySoundId = "playSound"; +const char* const kStopSoundId = "stopSound"; +const char* const kSetFrequencyId = "setFrequency"; +static const char kMessageArgumentSeparator = ':'; + +const double kDefaultFrequency = 440.0; +const double kPi = 3.141592653589; +const double kTwoPi = 2.0 * kPi; +// The sample count we will request. +const uint32_t kSampleFrameCount = 4096u; +// Only supporting stereo audio for now. +const uint32_t kChannels = 2u; +} // namespace + +namespace sine_synth { +// The Instance class. One of these exists for each instance of your NaCl +// module on the web page. The browser will ask the Module object to create +// a new Instance for each occurrence of the <embed> tag that has these +// attributes: +// type="application/x-nacl" +// src="sine_synth.nmf" +class SineSynthInstance : public pp::Instance { + public: + explicit SineSynthInstance(PP_Instance instance) + : pp::Instance(instance), + frequency_(kDefaultFrequency), + theta_(0), + sample_frame_count_(kSampleFrameCount) {} + virtual ~SineSynthInstance() {} + + // Called by the browser once the NaCl module is loaded and ready to + // initialize. Creates a Pepper audio context and initializes it. Returns + // true on success. Returning false causes the NaCl module to be deleted and + // no other functions to be called. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Called by the browser to handle the postMessage() call in Javascript. + // |var_message| is expected to be a string that contains the name of the + // method to call. Note that the setFrequency method takes a single + // parameter, the frequency. The frequency parameter is encoded as a string + // and appended to the 'setFrequency' method name after a ':'. Examples + // of possible message strings are: + // playSound + // stopSound + // setFrequency:880 + // If |var_message| is not a recognized method name, this method does nothing. + virtual void HandleMessage(const pp::Var& var_message); + + // Set the frequency of the sine wave to |frequency|. Posts a message back + // to the browser with the new frequency value. + void SetFrequency(double frequency); + + // The frequency property accessor. + double frequency() const { return frequency_; } + + private: + static void SineWaveCallback(void* samples, + uint32_t buffer_size, + void* data) { + SineSynthInstance* sine_synth_instance = + reinterpret_cast<SineSynthInstance*>(data); + const double frequency = sine_synth_instance->frequency(); + const double delta = kTwoPi * frequency / PP_AUDIOSAMPLERATE_44100; + const int16_t max_int16 = std::numeric_limits<int16_t>::max(); + + int16_t* buff = reinterpret_cast<int16_t*>(samples); + + // Make sure we can't write outside the buffer. + assert(buffer_size >= (sizeof(*buff) * kChannels * + sine_synth_instance->sample_frame_count_)); + + for (size_t sample_i = 0; + sample_i < sine_synth_instance->sample_frame_count_; + ++sample_i, sine_synth_instance->theta_ += delta) { + // Keep theta_ from going beyond 2*Pi. + if (sine_synth_instance->theta_ > kTwoPi) { + sine_synth_instance->theta_ -= kTwoPi; + } + double sin_value(std::sin(sine_synth_instance->theta_)); + int16_t scaled_value = static_cast<int16_t>(sin_value * max_int16); + for (size_t channel = 0; channel < kChannels; ++channel) { + *buff++ = scaled_value; + } + } + } + + pp::Audio audio_; + double frequency_; + + // The last parameter sent to the sin function. Used to prevent sine wave + // skips on buffer boundaries. + double theta_; + + // The count of sample frames per channel in an audio buffer. + uint32_t sample_frame_count_; +}; + +bool SineSynthInstance::Init(uint32_t argc, + const char* argn[], + const char* argv[]) { + // Ask the device for an appropriate sample count size. + sample_frame_count_ = + pp::AudioConfig::RecommendSampleFrameCount(PP_AUDIOSAMPLERATE_44100, + kSampleFrameCount); + audio_ = pp::Audio(this, + pp::AudioConfig(this, + PP_AUDIOSAMPLERATE_44100, + sample_frame_count_), + SineWaveCallback, + this); + return true; +} + +void SineSynthInstance::HandleMessage(const pp::Var& var_message) { + if (!var_message.is_string()) { + return; + } + std::string message = var_message.AsString(); + if (message == kPlaySoundId) { + audio_.StartPlayback(); + } else if (message == kStopSoundId) { + audio_.StopPlayback(); + } else if (message.find(kSetFrequencyId) == 0) { + // The argument to setFrequency is everything after the first ':'. + size_t sep_pos = message.find_first_of(kMessageArgumentSeparator); + if (sep_pos != std::string::npos) { + std::string string_arg = message.substr(sep_pos + 1); + // Got the argument value as a string: try to convert it to a number. + std::istringstream stream(string_arg); + double double_value; + if (stream >> double_value) { + SetFrequency(double_value); + return; + } + } + } +} + +void SineSynthInstance::SetFrequency(double frequency) { + frequency_ = frequency; + PostMessage(pp::Var(frequency_)); +} + +// The Module class. The browser calls the CreateInstance() method to create +// an instance of your NaCl module on the web page. The browser creates a new +// instance for each <embed> tag with type="application/x-nacl". +class SineSynthModule : public pp::Module { + public: + SineSynthModule() : pp::Module() {} + ~SineSynthModule() {} + + // Create and return a HelloWorldInstance object. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new SineSynthInstance(instance); + } +}; + +} // namespace sine_synth + +// Factory function called by the browser when the module is first loaded. +// The browser keeps a singleton of this module. It calls the +// CreateInstance() method on the object you return to make instances. There +// is one instance per <embed> tag on the page. This is the main binding +// point for your NaCl module with the browser. +namespace pp { +Module* CreateModule() { + return new sine_synth::SineSynthModule(); +} +} // namespace pp diff --git a/native_client_sdk/src/examples/sine_synth/sine_synth.html b/native_client_sdk/src/examples/sine_synth/sine_synth.html new file mode 100644 index 0000000..3d1609b --- /dev/null +++ b/native_client_sdk/src/examples/sine_synth/sine_synth.html @@ -0,0 +1,80 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> +<!-- +Copyright (c) 2011 The Native Client Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> +<head> + <title>Sine Wave Synthesizer</title> + <script type="text/javascript"> + sineSynth = null; // Global application object. + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + sineSynth = document.getElementById('sineSynth'); + document.getElementById('frequency_field').value = 440; + } + + // Handle a message coming from the NaCl module. The message payload + // contains the frequency value. Update the frequency field with this + // value. + function handleMessage(message_event) { + document.getElementById('frequency_field').value = message_event.data; + } + + function toggleSound(flag) { + sineSynth.postMessage('setFrequency:' + + document.getElementById('frequency_field').value); + if (flag) { + sineSynth.postMessage('playSound'); + } else { + sineSynth.postMessage('stopSound'); + } + } + + function changeFrequency(freq) { + sineSynth.postMessage('setFrequency:' + freq); + } + </script> +</head> + +<body id="bodyId"> + <h1>Sine Wave Synthesizer</h1> + <p>Click the button to start and stop the sine wave playing.</p> + <button onclick="toggleSound(true)">Play</button> + <button onclick="toggleSound(false)">Stop</button> + <p>Enter the frequency of the sine wave:</p> + <input type="text" size="15" id="frequency_field" + value="#undef" onchange="changeFrequency(this.value)" /> + <!-- Load the published .nexe. This includes the 'src' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the runtime + ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module. + To load the debug versions of your .nexes, set the 'src' attribute to the + _dbg.nmf version of the manifest file. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="sineSynth" + width=0 height=0 + src="sine_synth.nmf" + type="application/x-nacl" /> + </div> +</body> +</html> diff --git a/native_client_sdk/src/examples/tumbler/bind.js b/native_client_sdk/src/examples/tumbler/bind.js new file mode 100644 index 0000000..92fbbd2 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/bind.js @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview This class implements an extension to Function object that + * lets you bind a scope for |this| to a function. + */ + +/** + * Bind a scope to a function. Used to bind an object to |this| for event + * handlers. + * @param {!Object} scope The scope in which the function executes. |scope| + * becomes |this| during function execution. + * @return {function} the bound version of the original function. + */ +Function.prototype.bind = function(scope) { + var boundContext = this; + return function() { + return boundContext.apply(scope, arguments); + } +} diff --git a/native_client_sdk/src/examples/tumbler/build.scons b/native_client_sdk/src/examples/tumbler/build.scons new file mode 100644 index 0000000..4c42b7c --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/build.scons @@ -0,0 +1,55 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import nacl_utils +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'), + install_subdir='tumbler', lib_prefix='..') +nacl_env.Append( + # Add a CPPPATH that enables the full-path #include directives, such as + # #include "examples/sine_synth/sine_synth.h" + CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))], + # Strict ANSI compliance. + EXTRA_CCFLAGS=['-pedantic'], + LIBS=['ppapi_gles2'], + ) + +sources = [ + 'cube.cc', + 'opengl_context.cc', + 'scripting_bridge.cc', + 'shader_util.cc', + 'transforms.cc', + 'tumbler.cc', + 'tumbler_module.cc', + ] + +opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'tumbler') + +# This target is used by the SDK build system to provide a prebuilt version +# of the example in the SDK installer. +nacl_env.InstallPrebuilt('tumbler') + +app_files = [ + 'tumbler.html', + 'tumbler.nmf', + 'bind.js', + 'dragger.js', + 'trackball.js', + 'tumbler.js', + 'vector3.js', + ] + +# Split the install of the .nexes from the other app sources so that the strip +# action is applied to the .nexes only. +install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'], + source=opt_nexes) +install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'], + source=app_files) +nacl_env.Alias('install', source=install_app + install_nexes) diff --git a/native_client_sdk/src/examples/tumbler/callback.h b/native_client_sdk/src/examples/tumbler/callback.h new file mode 100644 index 0000000..4d67262 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/callback.h @@ -0,0 +1,92 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_CALLBACK_H_ +#define EXAMPLES_TUMBLER_CALLBACK_H_ + +#include <map> +#include <string> +#include <vector> + +namespace tumbler { + +class ScriptingBridge; + +// Templates used to support method call-backs when a method or property is +// accessed from the browser code. + +// Class suite used to publish a method name to Javascript. Typical use is +// like this: +// photo::MethodCallback<Calculator>* calculate_callback_; +// calculate_callback_ = +// new scripting::MethodCallback<Calculator>(this, +// &Calculator::Calculate); +// bridge->AddMethodNamed("calculate", calculate_callback_); +// ... +// delete calculate_callback_; +// +// The caller must delete the callback. + +// Methods get parameters as a dictionary that maps parameter names to values. +typedef std::map<std::string, std::string> MethodParameter; + +// Pure virtual class used in STL containers. +class MethodCallbackExecutor { + public: + virtual ~MethodCallbackExecutor() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) = 0; +}; + +template <class T> +class MethodCallback : public MethodCallbackExecutor { + public: + typedef void (T::*Method)( + const ScriptingBridge& bridge, + const MethodParameter& parameters); + + MethodCallback(T* instance, Method method) + : instance_(instance), method_(method) {} + virtual ~MethodCallback() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) { + // Use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + ((this->instance_)->*(this->method_))(bridge, parameters); + } + + private: + T* instance_; + Method method_; +}; + +template <class T> +class ConstMethodCallback : public MethodCallbackExecutor { + public: + typedef void (T::*ConstMethod)( + const ScriptingBridge& bridge, + const MethodParameter& parameters) const; + + ConstMethodCallback(const T* instance, ConstMethod method) + : instance_(instance), const_method_(method) {} + virtual ~ConstMethodCallback() {} + virtual void Execute( + const ScriptingBridge& bridge, + const MethodParameter& parameters) { + // Use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + ((this->instance_)->*(this->const_method_))(bridge, parameters); + } + + private: + const T* instance_; + ConstMethod const_method_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_CALLBACK_H_ + diff --git a/native_client_sdk/src/examples/tumbler/cube.cc b/native_client_sdk/src/examples/tumbler/cube.cc new file mode 100644 index 0000000..c062c81 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/cube.cc @@ -0,0 +1,267 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/tumbler/cube.h" + +#include <algorithm> + +#include "examples/tumbler/shader_util.h" +#include "examples/tumbler/transforms.h" + +namespace tumbler { + +static const size_t kVertexCount = 24; +static const int kIndexCount = 36; + +Cube::Cube(SharedOpenGLContext opengl_context) + : opengl_context_(opengl_context), + width_(1), + height_(1) { + eye_[0] = eye_[1] = 0.0f; + eye_[2] = 2.0f; + orientation_[0] = 0.0f; + orientation_[1] = 0.0f; + orientation_[2] = 0.0f; + orientation_[3] = 1.0f; +} + +Cube::~Cube() { + glDeleteBuffers(3, cube_vbos_); + glDeleteProgram(shader_program_object_); +} + +void Cube::PrepareOpenGL() { + CreateShaders(); + CreateCube(); + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glEnable(GL_DEPTH_TEST); +} + +void Cube::Resize(int width, int height) { + width_ = std::max(width, 1); + height_ = std::max(height, 1); + // Set the viewport + glViewport(0, 0, width_, height_); + // Compute the perspective projection matrix with a 60 degree FOV. + GLfloat aspect = static_cast<GLfloat>(width_) / static_cast<GLfloat>(height_); + transform_4x4::LoadIdentity(perspective_proj_); + transform_4x4::Perspective(perspective_proj_, 60.0f, aspect, 1.0f, 20.0f); +} + +void Cube::Draw() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Compute a new model-view matrix, then use that to make the composite + // model-view-projection matrix: MVP = MV . P. + GLfloat model_view[16]; + ComputeModelViewTransform(model_view); + transform_4x4::Multiply(mvp_matrix_, model_view, perspective_proj_); + + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]); + glUseProgram(shader_program_object_); + glEnableVertexAttribArray(position_location_); + glVertexAttribPointer(position_location_, + 3, + GL_FLOAT, + GL_FALSE, + 3 * sizeof(GLfloat), + NULL); + glEnableVertexAttribArray(color_location_); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]); + glVertexAttribPointer(color_location_, + 3, + GL_FLOAT, + GL_FALSE, + 3 * sizeof(GLfloat), + NULL); + glUniformMatrix4fv(mvp_location_, 1, GL_FALSE, mvp_matrix_); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]); + glDrawElements(GL_TRIANGLES, kIndexCount, GL_UNSIGNED_SHORT, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +bool Cube::CreateShaders() { + const char vertex_shader_src[] = + "uniform mat4 u_mvpMatrix; \n" + "attribute vec4 a_position; \n" + "attribute vec3 a_color; \n" + "varying lowp vec4 v_color; \n" + "void main() \n" + "{ \n" + " v_color.xyz = a_color; \n" + " v_color.w = 1.0; \n" + " gl_Position = u_mvpMatrix * a_position; \n" + "} \n"; + + const char fragment_shader_src[] = + "varying lowp vec4 v_color; \n" + "void main() \n" + "{ \n" + " gl_FragColor = v_color; \n" + "} \n"; + + // Load the shaders and get a linked program object + shader_program_object_ = + shader_util::CreateProgramFromVertexAndFragmentShaders( + vertex_shader_src, fragment_shader_src); + if (shader_program_object_ == 0) + return false; + position_location_ = glGetAttribLocation(shader_program_object_, + "a_position"); + color_location_ = glGetAttribLocation(shader_program_object_, "a_color"); + mvp_location_ = glGetUniformLocation(shader_program_object_, "u_mvpMatrix"); + return true; +} + +void Cube::CreateCube() { + static const GLfloat cube_vertices[] = { + // Vertex coordinates interleaved with color values + // Bottom + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, -0.5f, + // Top + -0.5f, 0.5f, -0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f, + // Back + -0.5f, -0.5f, -0.5f, + -0.5f, 0.5f, -0.5f, + 0.5f, 0.5f, -0.5f, + 0.5f, -0.5f, -0.5f, + // Front + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + // Left + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + -0.5f, 0.5f, -0.5f, + // Right + 0.5f, -0.5f, -0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f + }; + + static const GLfloat cube_colors[] = { + // Vertex coordinates interleaved with color values + // Bottom + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + // Top + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 1.0, 0.0, + // Back + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, + // Front + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + // Left + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + // Right + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0, + 0.0, 1.0, 1.0 + }; + + static const GLushort cube_indices[] = { + // Bottom + 0, 2, 1, + 0, 3, 2, + // Top + 4, 5, 6, + 4, 6, 7, + // Back + 8, 9, 10, + 8, 10, 11, + // Front + 12, 15, 14, + 12, 14, 13, + // Left + 16, 17, 18, + 16, 18, 19, + // Right + 20, 23, 22, + 20, 22, 21 + }; + + // Generate the VBOs and upload them to the graphics context. + glGenBuffers(3, cube_vbos_); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]); + glBufferData(GL_ARRAY_BUFFER, + kVertexCount * sizeof(GLfloat) * 3, + cube_vertices, + GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]); + glBufferData(GL_ARRAY_BUFFER, + kVertexCount * sizeof(GLfloat) * 3, + cube_colors, + GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + kIndexCount * sizeof(GL_UNSIGNED_SHORT), + cube_indices, + GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +void Cube::ComputeModelViewTransform(GLfloat* model_view) { + // This method takes into account the possiblity that |orientation_| + // might not be normalized. + double sqrx = orientation_[0] * orientation_[0]; + double sqry = orientation_[1] * orientation_[1]; + double sqrz = orientation_[2] * orientation_[2]; + double sqrw = orientation_[3] * orientation_[3]; + double sqrLength = 1.0 / (sqrx + sqry + sqrz + sqrw); + + transform_4x4::LoadIdentity(model_view); + model_view[0] = (sqrx - sqry - sqrz + sqrw) * sqrLength; + model_view[5] = (-sqrx + sqry - sqrz + sqrw) * sqrLength; + model_view[10] = (-sqrx - sqry + sqrz + sqrw) * sqrLength; + + double temp1 = orientation_[0] * orientation_[1]; + double temp2 = orientation_[2] * orientation_[3]; + model_view[1] = 2.0 * (temp1 + temp2) * sqrLength; + model_view[4] = 2.0 * (temp1 - temp2) * sqrLength; + + temp1 = orientation_[0] * orientation_[2]; + temp2 = orientation_[1] * orientation_[3]; + model_view[2] = 2.0 * (temp1 - temp2) * sqrLength; + model_view[8] = 2.0 * (temp1 + temp2) * sqrLength; + temp1 = orientation_[1] * orientation_[2]; + temp2 = orientation_[0] * orientation_[3]; + model_view[6] = 2.0 * (temp1 + temp2) * sqrLength; + model_view[9] = 2.0 * (temp1 - temp2) * sqrLength; + model_view[3] = 0.0; + model_view[7] = 0.0; + model_view[11] = 0.0; + + // Concatenate the translation to the eye point. + model_view[12] = -eye_[0]; + model_view[13] = -eye_[1]; + model_view[14] = -eye_[2]; + model_view[15] = 1.0; +} + +} // namespace tumbler diff --git a/native_client_sdk/src/examples/tumbler/cube.h b/native_client_sdk/src/examples/tumbler/cube.h new file mode 100644 index 0000000..6a992c7 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/cube.h @@ -0,0 +1,97 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_CUBE_H_ +#define EXAMPLES_TUMBLER_CUBE_H_ + +#include <GLES2/gl2.h> +#include <vector> +#include "examples/tumbler/opengl_context.h" +#include "examples/tumbler/opengl_context_ptrs.h" + +namespace tumbler { + +// The Cube class provides a place to implement 3D rendering. It has a +// frame that it occupies in a browser window. +class Cube { + public: + explicit Cube(SharedOpenGLContext opengl_context); + ~Cube(); + + // Called once when a new RenderContext is first bound to the view. The + // bound context is guaranteed to be current and valid before calling this + // method. + void PrepareOpenGL(); + + // Called whenever the size of the browser view changes. This method is + // called at least once when the view is first made visible. Clamps the + // sizes to 1. + void Resize(int width, int height); + + // Called every time the view need to be drawn. The bound context is + // guaranteed to be current and valid before this method is called. The + // visible portion of the context is flushed to the browser after this + // method returns. + void Draw(); + + // Accessor for width and height. To change these, call Resize. + const int width() const { + return width_; + } + + const int height() const { + return height_; + } + + // Accessor/mutator for the camera orientation. + void GetOrientation(std::vector<float>* orientation) const { + if (!orientation) + return; + (*orientation)[0] = static_cast<float>(orientation_[0]); + (*orientation)[1] = static_cast<float>(orientation_[1]); + (*orientation)[2] = static_cast<float>(orientation_[2]); + (*orientation)[3] = static_cast<float>(orientation_[3]); + } + void SetOrientation(const std::vector<float>& orientation) { + orientation_[0] = static_cast<GLfloat>(orientation[0]); + orientation_[1] = static_cast<GLfloat>(orientation[1]); + orientation_[2] = static_cast<GLfloat>(orientation[2]); + orientation_[3] = static_cast<GLfloat>(orientation[3]); + } + + private: + // Create the shaders used to draw the cube, and link them into a program. + // Initializes |shader_progam_object_|, |position_loction_| and + // |mvp_location_|. + bool CreateShaders(); + + // Generates a cube as a series of GL_TRIANGLE_STRIPs, and initializes + // |index_count_| to the number of indices in the index list used as a VBO. + // Creates the |vbo_ids_| required for the vertex and index data and uploads + // the the VBO data. + void CreateCube(); + + // Build up the model-view transform from the eye and orienation properties. + // Assumes that |model_view| is a 4x4 matrix. + void ComputeModelViewTransform(GLfloat* model_view); + + SharedOpenGLContext opengl_context_; + int width_; + int height_; + GLuint shader_program_object_; // The compiled shaders. + GLint position_location_; // The position attribute location. + GLint color_location_; // The color attribute location. + GLint mvp_location_; // The Model-View-Projection composite matrix. + GLuint cube_vbos_[3]; + GLfloat eye_[3]; // The eye point of the virtual camera. + // The orientation of the virtual camera stored as a quaternion. The + // quaternion is laid out as {{x, y, z}, w}. + GLfloat orientation_[4]; + GLfloat perspective_proj_[16]; + GLfloat mvp_matrix_[16]; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_CUBE_H_ diff --git a/native_client_sdk/src/examples/tumbler/dragger.js b/native_client_sdk/src/examples/tumbler/dragger.js new file mode 100644 index 0000000..232d8b5 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/dragger.js @@ -0,0 +1,134 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview This class implements a mouse-drag event. It registers for + * mousedown events, and when it sees one, starts capturing mousemove events + * until it gets a mousup event. It manufactures three drag events: the + * DRAG_START, DRAG and DRAG_END. + */ + +// Requires bind + +/** + * Constructor for the Dragger. Register for mousedown events that happen on + * |opt_target|. If |opt_target| is null or undefined, then this object + * observes mousedown on the whole document. + * @param {?Element} opt_target The event target. Defaults to the whole + * document. + * @constructor + */ +tumbler.Dragger = function(opt_target) { + /** + * The event target. + * @type {Element} + * @private + */ + this.target_ = opt_target || document; + + /** + * The array of objects that get notified of drag events. Each object in + * this array get sent a handleStartDrag(), handleDrag() and handleEndDrag() + * message. + * @type {Array.<Object>} + * @private + */ + this.listeners_ = []; + + /** + * Flag to indicate whether the object is in a drag sequence or not. + * @type {boolean} + * @private + */ + this.isDragging_ = false; + + /** + * The function objects that get attached as event handlers. These are + * cached so that they can be removed on mouse up. + * @type {function} + * @private + */ + this.boundMouseMove_ = null; + this.boundMouseUp_ = null; + + this.target_.addEventListener('mousedown', + this.onMouseDown.bind(this), + false); +} + +/** + * The ids used for drag event types. + * @enum {string} + */ +tumbler.Dragger.DragEvents = { + DRAG_START: 'dragstart', // Start a drag sequence + DRAG: 'drag', // Mouse moved during a drag sequence. + DRAG_END: 'dragend' // End a drag sewquence. +}; + +/** + * Add a drag listener. Each listener should respond to thhree methods: + * handleStartDrag(), handleDrag() and handleEndDrag(). This method assumes + * that |listener| does not already exist in the array of listeners. + * @param {!Object} listener The object that will listen to drag events. + */ +tumbler.Dragger.prototype.addDragListener = function(listener) { + this.listeners_.push(listener); +} + +/** + * Handle a mousedown event: register for mousemove and mouseup, then tell + * the target that is has a DRAG_START event. + * @param {Event} event The mousedown event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseDown = function(event) { + this.boundMouseMove_ = this.onMouseMove.bind(this); + this.boundMouseUp_ = this.onMouseUp.bind(this); + this.target_.addEventListener('mousemove', this.boundMouseMove_); + this.target_.addEventListener('mouseup', this.boundMouseUp_); + this.isDragging_ = true; + var dragStartEvent = { type: tumbler.Dragger.DragEvents.DRAG_START, + clientX: event.offsetX, + clientY: event.offsetY }; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleStartDrag(this.target_, dragStartEvent); + } +} + +/** + * Handle a mousemove event: tell the target that is has a DRAG event. + * @param {Event} event The mousemove event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseMove = function(event) { + if (!this.isDragging_) + return; + var dragEvent = { type: tumbler.Dragger.DragEvents.DRAG, + clientX: event.offsetX, + clientY: event.offsetY}; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleDrag(this.target_, dragEvent); + } +} + +/** + * Handle a mouseup event: un-register for mousemove and mouseup, then tell + * the target that is has a DRAG_END event. + * @param {Event} event The mouseup event that triggered this method. + */ +tumbler.Dragger.prototype.onMouseUp = function(event) { + this.target_.removeEventListener('mouseup', this.boundMouseUp_, false); + this.target_.removeEventListener('mousemove', this.boundMouseMove_, false); + this.boundMouseUp_ = null; + this.boundMouseMove_ = null; + this.isDragging_ = false; + var dragEndEvent = { type: tumbler.Dragger.DragEvents.DRAG_END, + clientX: event.offsetX, + clientY: event.offsetY}; + var i; + for (i = 0; i < this.listeners_.length; ++i) { + this.listeners_[i].handleEndDrag(this.target_, dragEndEvent); + } +} diff --git a/native_client_sdk/src/examples/tumbler/opengl_context.cc b/native_client_sdk/src/examples/tumbler/opengl_context.cc new file mode 100644 index 0000000..f59013b --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/opengl_context.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/tumbler/opengl_context.h" + +#include <pthread.h> +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/gles2/gl2ext_ppapi.h" + +namespace { +// This is called by the brower when the 3D context has been flushed to the +// browser window. +void FlushCallback(void* data, int32_t result) { + static_cast<tumbler::OpenGLContext*>(data)->set_flush_pending(false); +} +} // namespace + +namespace tumbler { + +OpenGLContext::OpenGLContext(pp::Instance* instance) + : pp::Graphics3DClient(instance), + flush_pending_(false) { + pp::Module* module = pp::Module::Get(); + assert(module); + gles2_interface_ = static_cast<const struct PPB_OpenGLES2*>( + module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE)); + assert(gles2_interface_); +} + +OpenGLContext::~OpenGLContext() { + glSetCurrentContextPPAPI(0); +} + +bool OpenGLContext::MakeContextCurrent(pp::Instance* instance) { + if (instance == NULL) { + glSetCurrentContextPPAPI(0); + return false; + } + // Lazily create the Pepper context. + if (context_.is_null()) { + int32_t attribs[] = { + PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8, + PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24, + PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 8, + PP_GRAPHICS3DATTRIB_SAMPLES, 0, + PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0, + PP_GRAPHICS3DATTRIB_WIDTH, size_.width(), + PP_GRAPHICS3DATTRIB_HEIGHT, size_.height(), + PP_GRAPHICS3DATTRIB_NONE + }; + context_ = pp::Graphics3D(instance, pp::Graphics3D(), attribs); + if (context_.is_null()) { + glSetCurrentContextPPAPI(0); + return false; + } + instance->BindGraphics(context_); + } + glSetCurrentContextPPAPI(context_.pp_resource()); + return true; +} + +void OpenGLContext::InvalidateContext(pp::Instance* instance) { + glSetCurrentContextPPAPI(0); +} + +void OpenGLContext::ResizeContext(const pp::Size& size) { + size_ = size; + if (!context_.is_null()) { + context_.ResizeBuffers(size.width(), size.height()); + } +} + + +void OpenGLContext::FlushContext() { + if (flush_pending()) { + // A flush is pending so do nothing; just drop this flush on the floor. + return; + } + set_flush_pending(true); + context_.SwapBuffers(pp::CompletionCallback(&FlushCallback, this)); +} +} // namespace tumbler + diff --git a/native_client_sdk/src/examples/tumbler/opengl_context.h b/native_client_sdk/src/examples/tumbler/opengl_context.h new file mode 100644 index 0000000..b75060d --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/opengl_context.h @@ -0,0 +1,93 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ +#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ + +/// +/// @file +/// OpenGLContext manages the OpenGL context in the browser that is associated +/// with a @a pp::Instance instance. +/// + +#include <assert.h> +#include <pthread.h> + +#include <algorithm> +#include <string> + +#include "examples/tumbler/opengl_context_ptrs.h" +#include "ppapi/c/ppb_opengles2.h" +#include "ppapi/cpp/graphics_3d_client.h" +#include "ppapi/cpp/graphics_3d.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/size.h" + +namespace tumbler { + +/// OpenGLContext manages an OpenGL rendering context in the browser. +/// +class OpenGLContext : public pp::Graphics3DClient { + public: + explicit OpenGLContext(pp::Instance* instance); + + /// Release all the in-browser resources used by this context, and make this + /// context invalid. + virtual ~OpenGLContext(); + + /// The Graphics3DClient interfcace. + virtual void Graphics3DContextLost() { + assert(!"Unexpectedly lost graphics context"); + } + + /// Make @a this the current 3D context in @a instance. + /// @param instance The instance of the NaCl module that will receive the + /// the current 3D context. + /// @return success. + bool MakeContextCurrent(pp::Instance* instance); + + /// Flush the contents of this context to the browser's 3D device. + void FlushContext(); + + /// Make the underlying 3D device invalid, so that any subsequent rendering + /// commands will have no effect. The next call to MakeContextCurrent() will + /// cause the underlying 3D device to get rebound and start receiving + /// receiving rendering commands again. Use InvalidateContext(), for + /// example, when resizing the context's viewing area. + void InvalidateContext(pp::Instance* instance); + + /// Resize the context. + void ResizeContext(const pp::Size& size); + + /// The OpenGL ES 2.0 interface. + const struct PPB_OpenGLES2* gles2() const { + return gles2_interface_; + } + + /// The PP_Resource needed to make GLES2 calls through the Pepper interface. + const PP_Resource gl_context() const { + return context_.pp_resource(); + } + + /// Indicate whether a flush is pending. This can only be called from the + /// main thread; it is not thread safe. + bool flush_pending() const { + return flush_pending_; + } + void set_flush_pending(bool flag) { + flush_pending_ = flag; + } + + private: + pp::Size size_; + pp::Graphics3D context_; + bool flush_pending_; + + const struct PPB_OpenGLES2* gles2_interface_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_ + diff --git a/native_client_sdk/src/examples/tumbler/opengl_context_ptrs.h b/native_client_sdk/src/examples/tumbler/opengl_context_ptrs.h new file mode 100644 index 0000000..3478521 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/opengl_context_ptrs.h @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ +#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ + +// A convenience wrapper for a shared OpenGLContext pointer type. As other +// smart pointer types are needed, add them here. + +#include <tr1/memory> + +namespace tumbler { + +class OpenGLContext; + +typedef std::tr1::shared_ptr<OpenGLContext> SharedOpenGLContext; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_ + diff --git a/native_client_sdk/src/examples/tumbler/scripting_bridge.cc b/native_client_sdk/src/examples/tumbler/scripting_bridge.cc new file mode 100644 index 0000000..e74bd9e --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/scripting_bridge.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/tumbler/scripting_bridge.h" + +namespace { +const char* const kWhiteSpaceCharacters = " \t"; + +// Helper function to pull out the next token in |token_string|. A token is +// delimited by whitespace. Scanning begins at |*pos|, if pos goes beyond the +// end of |token_string|, it is set to std::string::npos and an empty string +// is returned. On return, |*pos| will point to the beginning of the next +// token. |pos| must not be NULL. +const std::string ScanToken(const std::string& token_string, size_t* pos) { + std::string token; + if (*pos == std::string::npos) { + return token; + } + size_t token_start_pos = token_string.find_first_not_of(kWhiteSpaceCharacters, + *pos); + size_t token_end_pos = token_string.find_first_of(kWhiteSpaceCharacters, + token_start_pos); + if (token_start_pos != std::string::npos) { + token = token_string.substr(token_start_pos, token_end_pos); + } + *pos = token_end_pos; + return token; +} + +// Take a string of the form 'name:value' and split it into two strings, one +// containing 'name' and the other 'value'. If the ':' separator is missing, +// or is the last character in |parameter|, |parameter| is copied to +// |param_name|, |param_value| is left unchanged and false is returned. +bool ParseParameter(const std::string& parameter, + std::string* param_name, + std::string* param_value) { + bool success = false; + size_t sep_pos = parameter.find_first_of(':'); + if (sep_pos != std::string::npos) { + *param_name = parameter.substr(0, sep_pos); + if (sep_pos < parameter.length() - 1) { + *param_value = parameter.substr(sep_pos + 1); + success = true; + } else { + success = false; + } + } else { + *param_name = parameter; + success = false; + } + return success; +} +} // namespace + +namespace tumbler { + +bool ScriptingBridge::AddMethodNamed(const std::string& method_name, + SharedMethodCallbackExecutor method) { + if (method_name.size() == 0 || method == NULL) + return false; + method_dictionary_.insert( + std::pair<std::string, SharedMethodCallbackExecutor>(method_name, + method)); + return true; +} + +bool ScriptingBridge::InvokeMethod(const std::string& method) { + size_t current_pos = 0; + const std::string method_name = ScanToken(method, ¤t_pos); + MethodDictionary::iterator method_iter; + method_iter = method_dictionary_.find(method_name); + if (method_iter != method_dictionary_.end()) { + // Pull out the method parameters and build a dictionary that maps + // parameter names to values. + std::map<std::string, std::string> param_dict; + while (current_pos != std::string::npos) { + const std::string parameter = ScanToken(method, ¤t_pos); + if (parameter.length()) { + std::string param_name; + std::string param_value; + if (ParseParameter(parameter, ¶m_name, ¶m_value)) { + // Note that duplicate parameter names will override each other. The + // last one in the method string will be used. + param_dict[param_name] = param_value; + } + } + } + (*method_iter->second).Execute(*this, param_dict); + return true; + } + return false; +} + +} // namespace tumbler diff --git a/native_client_sdk/src/examples/tumbler/scripting_bridge.h b/native_client_sdk/src/examples/tumbler/scripting_bridge.h new file mode 100644 index 0000000..3e2f73b --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/scripting_bridge.h @@ -0,0 +1,52 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ +#define EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ + +#include <map> +#include <string> +#include <tr1/memory> +#include <vector> + +#include "examples/tumbler/callback.h" +#include "ppapi/cpp/var.h" + +namespace tumbler { + +class MethodCallbackExecutor; + +// This class handles the interface between the browser and the NaCl module. +// There is a single point of entry from the browser: postMessage(). The +// string passed to postMessage() has this format: +// 'function_name arg_name0:arg_0 arg_name1:arg1 ...' +// The arguments have undetermined type; they are placed in a map of argument +// names and values. Values are all strings, it is up to the target code to +// do any type coercion. +// Methods called by the scripting bridge must have a signature like this: +// void Method(const ScriptingBridge& bridge, +// const ParameterDictionary&); +class ScriptingBridge { + public: + // Shared pointer type used in the method map. + typedef std::tr1::shared_ptr<MethodCallbackExecutor> + SharedMethodCallbackExecutor; + + virtual ~ScriptingBridge() {} + + // Causes |method_name| to be published as a method that can be called via + // postMessage() from the browser. Associates this method with |method|. + bool AddMethodNamed(const std::string& method_name, + SharedMethodCallbackExecutor method); + + bool InvokeMethod(const std::string& method); + + private: + typedef std::map<std::string, SharedMethodCallbackExecutor> MethodDictionary; + + MethodDictionary method_dictionary_; +}; + +} // namespace tumbler +#endif // EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_ diff --git a/native_client_sdk/src/examples/tumbler/shader_util.cc b/native_client_sdk/src/examples/tumbler/shader_util.cc new file mode 100644 index 0000000..2bbfc84 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/shader_util.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/tumbler/shader_util.h" + +#include <stdlib.h> +#include <stdio.h> + +namespace shader_util { + +GLuint CreateShaderOfType(GLenum type, const char *shader_src) { + GLuint shader; + GLint compiled; + + // Create the shader object + shader = glCreateShader(type); + + if (shader == 0) + return 0; + + // Load and compile the shader source + glShaderSource(shader, 1, &shader_src, NULL); + glCompileShader(shader); + + // Check the compile status + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (compiled == 0) { + GLint info_len = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len); + if (info_len > 1) { + char* info_log = reinterpret_cast<char*>(malloc(sizeof(char) * info_len)); + glGetShaderInfoLog(shader, info_len, NULL, info_log); + // TODO(dspringer): We could really use a logging API. + printf("Error compiling shader:\n%s\n", info_log); + free(info_log); + } + glDeleteShader(shader); + return 0; + } + + return shader; +} + +GLuint CreateProgramFromVertexAndFragmentShaders( + const char *vertex_shader_src, const char *fragment_shader_src) { + GLuint vertex_shader; + GLuint fragment_shader; + GLuint program_object; + GLint linked; + + // Load the vertex/fragment shaders + vertex_shader = CreateShaderOfType(GL_VERTEX_SHADER, vertex_shader_src); + if (vertex_shader == 0) + return 0; + fragment_shader = CreateShaderOfType(GL_FRAGMENT_SHADER, fragment_shader_src); + if (fragment_shader == 0) { + glDeleteShader(vertex_shader); + return 0; + } + + // Create the program object and attach the shaders. + program_object = glCreateProgram(); + if (program_object == 0) + return 0; + glAttachShader(program_object, vertex_shader); + glAttachShader(program_object, fragment_shader); + + // Link the program + glLinkProgram(program_object); + + // Check the link status + glGetProgramiv(program_object, GL_LINK_STATUS, &linked); + if (linked == 0) { + GLint info_len = 0; + glGetProgramiv(program_object, GL_INFO_LOG_LENGTH, &info_len); + if (info_len > 1) { + char* info_log = reinterpret_cast<char*>(malloc(info_len)); + glGetProgramInfoLog(program_object, info_len, NULL, info_log); + // TODO(dspringer): We could really use a logging API. + printf("Error linking program:\n%s\n", info_log); + free(info_log); + } + glDeleteProgram(program_object); + return 0; + } + + // Delete these here because they are attached to the program object. + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return program_object; +} + +} // namespace shader_util diff --git a/native_client_sdk/src/examples/tumbler/shader_util.h b/native_client_sdk/src/examples/tumbler/shader_util.h new file mode 100644 index 0000000..635b16b --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/shader_util.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Some simple helper functions that load shaders and create program objects. + +#ifndef EXAMPLES_TUMBLER_SHADER_UTIL_H_ +#define EXAMPLES_TUMBLER_SHADER_UTIL_H_ + +#include <GLES2/gl2.h> + +namespace shader_util { + +// Load and compile a shader. |type| can be one of GL_VERTEX_SHADER or +// GL_FRAGMENT_SHADER. Returns a non-0 value representing the compiled +// shader on success, 0 on failure. The caller is responsible for deleting +// the returned shader using glDeleteShader(). +GLuint CreateShaderOfType(GLenum type, const char *shader_src); + +// Load and compile the vertex and fragment shaders, then link these together +// into a complete program. Returns a non-0 value representing the program on, +// success or 0 on failure. The caller is responsible for deleting the +// returned program using glDeleteProgram(). +GLuint CreateProgramFromVertexAndFragmentShaders( + const char *vertex_shader_src, const char *fragment_shader_src); + +} // namespace shader_util + +#endif // EXAMPLES_TUMBLER_SHADER_UTIL_H_ diff --git a/native_client_sdk/src/examples/tumbler/trackball.js b/native_client_sdk/src/examples/tumbler/trackball.js new file mode 100644 index 0000000..88b9a62 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/trackball.js @@ -0,0 +1,296 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Implement a virtual trackball in the tumbler.Trackball + * class. This class maps 2D mouse events to 3D rotations by simulating a + * trackball that you roll by dragging the mouse. There are two principle + * methods in the class: startAtPointInFrame which you use to begin a trackball + * simulation and rollToPoint, which you use while dragging the mouse. The + * rollToPoint method returns a rotation expressed as a quaternion. + */ + + +// Requires tumbler.Application +// Requires tumbler.DragEvent +// Requires tumbler.Vector3 + +/** + * Constructor for the Trackball object. This class maps 2D mouse drag events + * into 3D rotations by simulating a trackball. The idea is to simulate + * clicking on the trackball, and then rolling it as you drag the mouse. + * The math behind the trackball is simple: start with a vector from the first + * mouse-click on the ball to the center of the 3D view. At the same time, set + * the radius of the ball to be the smaller dimension of the 3D view. As you + * drag the mouse around in the 3D view, a second vector is computed from the + * surface of the ball to the center. The axis of rotation is the cross + * product of these two vectors, and the angle of rotation is the angle between + * the two vectors. + * @constructor + */ +tumbler.Trackball = function() { + /** + * The square of the trackball's radius. The math never looks at the radius, + * but looks at the radius squared. + * @type {number} + * @private + */ + this.sqrRadius_ = 0; + + /** + * The 3D vector representing the point on the trackball where the mouse + * was clicked. Default is pointing stright through the center of the ball. + * @type {Object} + * @private + */ + this.rollStart_ = new tumbler.Vector3(0, 0, 1); + + /** + * The 2D center of the frame that encloses the trackball. + * @type {!Object} + * @private + */ + this.center_ = { x: 0, y: 0 }; + + /** + * Cached camera orientation. When a drag START event happens this is set to + * the current orientation in the calling view's plugin. The default is the + * identity quaternion. + * @type {Array.<number>} + * @private + */ + this.cameraOrientation_ = [0, 0, 0, 1]; +}; + +/** + * Compute the dimensions of the virtual trackball to fit inside |frameSize|. + * The radius of the trackball is set to be 1/2 of the smaller of the two frame + * dimensions, the center point is at the midpoint of each side. + * @param {!goog.math.Size} frameSize 2D-point representing the size of the + * element that encloses the virtual trackball. + * @private + */ +tumbler.Trackball.prototype.initInFrame_ = function(frameSize) { + // Compute the radius of the virtual trackball. This is 1/2 of the smaller + // of the frame's width and height. + var halfFrameSize = 0.5 * Math.min(frameSize.width, frameSize.height); + // Cache the square of the trackball's radius. + this.sqrRadius_ = halfFrameSize * halfFrameSize; + // Figure the center of the view. + this.center_.x = frameSize.width * 0.5; + this.center_.y = frameSize.height * 0.5; +}; + +/** + * Method to convert (by translation) a 2D client point from a coordinate space + * with origin in the lower-left corner of the client view to a space with + * origin in the center of the client view. Use this method before mapping the + * 2D point to he 3D tackball point (see also the projectOnTrackball_() method). + * Call the startAtPointInFrame before calling this method so that the + * |center_| property is correctly initialized. + * @param {!Object} clientPoint map this point to the coordinate space with + * origin in thecenter of the client view. + * @return {Object} the converted point. + * @private + */ +tumbler.Trackball.prototype.convertClientPoint_ = function(clientPoint) { + var difference = { x: clientPoint.x - this.center_.x, + y: clientPoint.y - this.center_.y } + return difference; +}; + +/** + * Method to map a 2D point to a 3D point on the virtual trackball that was set + * up using the startAtPointInFrame method. If the point lies outside of the + * radius of the virtual trackball, then the z-coordinate of the 3D point + * is set to 0. + * @param {!Object.<x, y>} point 2D-point in the coordinate space with origin + * in the center of the client view. + * @return {tumbler.Vector3} the 3D point on the virtual trackball. + * @private + */ +tumbler.Trackball.prototype.projectOnTrackball_ = function(point) { + var sqrRadius2D = point.x * point.x + point.y * point.y; + var zValue; + if (sqrRadius2D > this.sqrRadius_) { + // |point| lies outside the virtual trackball's sphere, so use a virtual + // z-value of 0. This is equivalent to clicking on the horizontal equator + // of the trackball. + zValue = 0; + } else { + // A sphere can be defined as: r^2 = x^2 + y^2 + z^2, so z = + // sqrt(r^2 - (x^2 + y^2)). + zValue = Math.sqrt(this.sqrRadius_ - sqrRadius2D); + } + var trackballPoint = new tumbler.Vector3(point.x, point.y, zValue); + return trackballPoint; +}; + +/** + * Method to start up the trackball. The trackball works by pretending that a + * ball encloses the 3D view. You roll this pretend ball with the mouse. For + * example, if you click on the center of the ball and move the mouse straight + * to the right, you roll the ball around its Y-axis. This produces a Y-axis + * rotation. You can click on the "edge" of the ball and roll it around + * in a circle to get a Z-axis rotation. + * @param {!Object.<x, y>} startPoint 2D-point, usually the mouse-down + * point. + * @param {!Object.<width, height>} frameSize 2D-point representing the size of + * the element that encloses the virtual trackball. + */ +tumbler.Trackball.prototype.startAtPointInFrame = + function(startPoint, frameSize) { + this.initInFrame_(frameSize); + // Compute the starting vector from the surface of the ball to its center. + this.rollStart_ = this.projectOnTrackball_( + this.convertClientPoint_(startPoint)); +}; + +/** + * Method to roll the virtual trackball; call this in response to a mouseDrag + * event. Takes |dragPoint| and projects it from 2D mouse coordinates onto the + * virtual track ball that was set up in startAtPointInFrame method. + * Returns a quaternion that represents the rotation from |rollStart_| to + * |rollEnd_|. + * @param {!Object.<x, y>} dragPoint 2D-point representing the + * destination mouse point. + * @return {Array.<number>} a quaternion that represents the rotation from + * the point wnere the mouse was clicked on the trackball to this point. + * The quaternion looks like this: [[v], cos(angle/2)], where [v] is the + * imaginary part of the quaternion and is computed as [x, y, z] * + * sin(angle/2). + */ +tumbler.Trackball.prototype.rollToPoint = function(dragPoint) { + var rollTo = this.convertClientPoint_(dragPoint); + if ((Math.abs(this.rollStart_.x - rollTo.x) < + tumbler.Trackball.DOUBLE_EPSILON) && + (Math.abs(this.rollStart_.y, rollTo.y) < + tumbler.Trackball.DOUBLE_EPSILON)) { + // Not enough change in the vectors to roll the ball, return the identity + // quaternion. + return [0, 0, 0, 1]; + } + + // Compute the ending vector from the surface of the ball to its center. + var rollEnd = this.projectOnTrackball_(rollTo); + + // Take the cross product of the two vectors. r = s X e + var rollVector = this.rollStart_.cross(rollEnd); + var invStartMag = 1.0 / this.rollStart_.magnitude(); + var invEndMag = 1.0 / rollEnd.magnitude(); + + // cos(a) = (s . e) / (||s|| ||e||) + var cosAng = this.rollStart_.dot(rollEnd) * invStartMag * invEndMag; + // sin(a) = ||(s X e)|| / (||s|| ||e||) + var sinAng = rollVector.magnitude() * invStartMag * invEndMag; + // Build a quaternion that represents the rotation about |rollVector|. + // Use atan2 for a better angle. If you use only cos or sin, you only get + // half the possible angles, and you can end up with rotations that flip + // around near the poles. + var rollHalfAngle = Math.atan2(sinAng, cosAng) * 0.5; + rollVector.normalize(); + // The quaternion looks like this: [[v], cos(angle/2)], where [v] is the + // imaginary part of the quaternion and is computed as [x, y, z] * + // sin(angle/2). + rollVector.scale(Math.sin(rollHalfAngle)); + var ballQuaternion = [rollVector.x, + rollVector.y, + rollVector.z, + Math.cos(rollHalfAngle)]; + return ballQuaternion; +}; + +/** + * Handle the drag START event: grab the current camera orientation from the + * sending view and set up the virtual trackball. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragStartEvent The DRAG_START event that + * triggered this handler. + */ +tumbler.Trackball.prototype.handleStartDrag = + function(controller, dragStartEvent) { + // Cache the camera orientation. The orientations from the trackball as it + // rolls are concatenated to this orientation and pushed back into the + // plugin on the other side of the JavaScript bridge. + controller.setCameraOrientation(this.cameraOrientation_); + // Invert the y-coordinate for the trackball computations. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragStartEvent.clientX, + y: frameSize.height - dragStartEvent.clientY }; + this.startAtPointInFrame(flippedY, frameSize); +}; + +/** + * Handle the drag DRAG event: concatenate the current orientation to the + * cached orientation. Send this final value through to the GSPlugin via the + * setValueForKey() method. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragEvent The DRAG event that triggered this + * handler. + */ +tumbler.Trackball.prototype.handleDrag = + function(controller, dragEvent) { + // Flip the y-coordinate so that the 2D origin is in the lower-left corner. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragEvent.clientX, + y: frameSize.height - dragEvent.clientY }; + controller.setCameraOrientation( + tumbler.multQuaternions(this.rollToPoint(flippedY), + this.cameraOrientation_)); +}; + +/** + * Handle the drag END event: get the final orientation and concatenate it to + * the cached orientation. + * @param {!tumbler.Application} view The view controller that called this + * method. + * @param {!tumbler.DragEvent} dragEndEvent The DRAG_END event that triggered + * this handler. + */ +tumbler.Trackball.prototype.handleEndDrag = + function(controller, dragEndEvent) { + // Flip the y-coordinate so that the 2D origin is in the lower-left corner. + var frameSize = { width: controller.offsetWidth, + height: controller.offsetHeight }; + var flippedY = { x: dragEndEvent.clientX, + y: frameSize.height - dragEndEvent.clientY }; + this.cameraOrientation_ = tumbler.multQuaternions(this.rollToPoint(flippedY), + this.cameraOrientation_); + controller.setCameraOrientation(this.cameraOrientation_); +}; + +/** + * A utility function to multiply two quaterions. Returns the product q0 * q1. + * This is effectively the same thing as concatenating the two rotations + * represented in each quaternion together. Note that quaternion multiplication + * is NOT commutative: q0 * q1 != q1 * q0. + * @param {!Array.<number>} q0 A 4-element array representing the first + * quaternion. + * @param {!Array.<number>} q1 A 4-element array representing the second + * quaternion. + * @return {Array.<number>} A 4-element array representing the product q0 * q1. + */ +tumbler.multQuaternions = function(q0, q1) { + // Return q0 * q1 (note the order). + var qMult = [ + q0[3] * q1[0] + q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1], + q0[3] * q1[1] - q0[0] * q1[2] + q0[1] * q1[3] + q0[2] * q1[0], + q0[3] * q1[2] + q0[0] * q1[1] - q0[1] * q1[0] + q0[2] * q1[3], + q0[3] * q1[3] - q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] + ]; + return qMult; +}; + +/** + * Real numbers that are less than this distance apart are considered + * equivalent. + * TODO(dspringer): It seems as though there should be a const like this + * in Closure somewhere (goog.math?). + * @type {number} + */ +tumbler.Trackball.DOUBLE_EPSILON = 1.0e-16; diff --git a/native_client_sdk/src/examples/tumbler/transforms.cc b/native_client_sdk/src/examples/tumbler/transforms.cc new file mode 100644 index 0000000..79cb9cf --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/transforms.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/tumbler/transforms.h" + +#include <math.h> +#include <string.h> +#include <GLES2/gl2.h> + +namespace transform_4x4 { + +static const GLfloat kPI = 3.1415926535897932384626433832795f; + +void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz) { + m[12] += (m[0] * tx + m[4] * ty + m[8] * tz); + m[13] += (m[1] * tx + m[5] * ty + m[9] * tz); + m[14] += (m[2] * tx + m[6] * ty + m[10] * tz); + m[15] += (m[3] * tx + m[7] * ty + m[11] * tz); +} + +void Frustum(GLfloat* m, + GLfloat left, + GLfloat right, + GLfloat bottom, + GLfloat top, + GLfloat near_z, + GLfloat far_z) { + GLfloat delta_x = right - left; + GLfloat delta_y = top - bottom; + GLfloat delta_z = far_z - near_z; + GLfloat frustum[16]; + + if ((near_z <= 0.0f) || (far_z <= 0.0f) || + (delta_x <= 0.0f) || (delta_y <= 0.0f) || (delta_z <= 0.0f)) + return; + + frustum[0] = 2.0f * near_z / delta_x; + frustum[1] = frustum[2] = frustum[3] = 0.0f; + + frustum[5] = 2.0f * near_z / delta_y; + frustum[4] = frustum[6] = frustum[7] = 0.0f; + + frustum[8] = (right + left) / delta_x; + frustum[9] = (top + bottom) / delta_y; + frustum[10] = -(near_z + far_z) / delta_z; + frustum[11] = -1.0f; + + frustum[14] = -2.0f * near_z * far_z / delta_z; + frustum[12] = frustum[13] = frustum[15] = 0.0f; + + transform_4x4::Multiply(m, frustum, m); +} + + +void Perspective(GLfloat* m, + GLfloat fovy, + GLfloat aspect, + GLfloat near_z, + GLfloat far_z) { + GLfloat frustum_w, frustum_h; + + frustum_h = tanf((fovy * 0.5f) / 180.0f * kPI) * near_z; + frustum_w = frustum_h * aspect; + transform_4x4::Frustum(m, -frustum_w, frustum_w, -frustum_h, frustum_h, + near_z, far_z); +} + +void Multiply(GLfloat *m, GLfloat *a, GLfloat* b) { + GLfloat tmp[16]; + // tmp = a . b + GLfloat a0, a1, a2, a3; + a0 = a[0]; + a1 = a[1]; + a2 = a[2]; + a3 = a[3]; + tmp[0] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[1] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[2] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[3] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[4]; + a1 = a[5]; + a2 = a[6]; + a3 = a[7]; + tmp[4] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[5] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[6] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[7] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[8]; + a1 = a[9]; + a2 = a[10]; + a3 = a[11]; + tmp[8] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[9] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[10] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[11] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + + a0 = a[12]; + a1 = a[13]; + a2 = a[14]; + a3 = a[15]; + tmp[12] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12]; + tmp[13] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13]; + tmp[14] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14]; + tmp[15] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15]; + memcpy(m, tmp, sizeof(GLfloat) * 4 * 4); +} + +void LoadIdentity(GLfloat* m) { + memset(m, 0, sizeof(GLfloat) * 4 * 4); + m[0] = m[5] = m[10] = m[15] = 1.0f; +} + +} // namespace transform_4x4 diff --git a/native_client_sdk/src/examples/tumbler/transforms.h b/native_client_sdk/src/examples/tumbler/transforms.h new file mode 100644 index 0000000..5ac3d6e --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/transforms.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_TRANSFORMS_H_ +#define EXAMPLES_TUMBLER_TRANSFORMS_H_ + +#include <GLES2/gl2.h> + +// A very simple set of 4x4 matrix routines. In all these routines, the input +// matrix is assumed to be a 4x4 of GLfloats. + +namespace transform_4x4 { + +// Pre-multply |m| with a projection transformation 4x4 matrix from a +// truncated pyramid viewing frustum. +void Frustum(GLfloat* m, + GLfloat left, + GLfloat right, + GLfloat bottom, + GLfloat top, + GLfloat near_z, + GLfloat far_z); + +// Replace |m| with the 4x4 identity matrix. +void LoadIdentity(GLfloat* m); + +// |m| <- |a| . |b|. |m| can point at the same memory as either |a| or |b|. +void Multiply(GLfloat *m, GLfloat *a, GLfloat* b); + +// Pre-multiply |m| with a single-point perspective matrix based on the viewing +// frustum whose view angle is |fovy|. +void Perspective(GLfloat* m, + GLfloat fovy, + GLfloat aspect, + GLfloat near_z, + GLfloat far_z); + +// Pre-multiply |m| with a matrix that represents a translation by |tx|, |ty|, +// |tz|. +void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz); +} // namespace transform_4x4 + +#endif // EXAMPLES_TUMBLER_TRANSFORMS_H_ + diff --git a/native_client_sdk/src/examples/tumbler/tumbler.cc b/native_client_sdk/src/examples/tumbler/tumbler.cc new file mode 100644 index 0000000..57343c4 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/tumbler.cc @@ -0,0 +1,137 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/tumbler/tumbler.h" + +#include <cstdlib> +#include <cstring> +#include <string> +#include <vector> + +#include "examples/tumbler/cube.h" +#include "examples/tumbler/opengl_context.h" +#include "examples/tumbler/scripting_bridge.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/size.h" +#include "ppapi/cpp/var.h" + +namespace { +const size_t kQuaternionElementCount = 4; +const char* const kArrayStartCharacter = "["; +const char* const kArrayEndCharacter = "]"; +const char* const kArrayDelimiter = ","; + +// Return the value of parameter named |param_name| from |parameters|. If +// |param_name| doesn't exist, then return an empty string. +std::string GetParameterNamed( + const std::string& param_name, + const tumbler::MethodParameter& parameters) { + tumbler::MethodParameter::const_iterator i = + parameters.find(param_name); + if (i == parameters.end()) { + return ""; + } + return i->second; +} + +// Convert the JSON string |array| into a vector of floats. |array| is +// expected to be a string bounded by '[' and ']', and a comma-delimited list +// of numbers. Any errors result in the return of an empty array. +std::vector<float> CreateArrayFromJSON(const std::string& json_array) { + std::vector<float> float_array; + size_t array_start_pos = json_array.find_first_of(kArrayStartCharacter); + size_t array_end_pos = json_array.find_last_of(kArrayEndCharacter); + if (array_start_pos == std::string::npos || + array_end_pos == std::string::npos) + return float_array; // Malformed JSON: missing '[' or ']'. + // Pull out the array elements. + size_t token_pos = array_start_pos + 1; + while (token_pos < array_end_pos) { + float_array.push_back(strtof(json_array.data() + token_pos, NULL)); + size_t delim_pos = json_array.find_first_of(kArrayDelimiter, token_pos); + if (delim_pos == std::string::npos) + break; + token_pos = delim_pos + 1; + } + return float_array; +} +} // namespace + +namespace tumbler { + +Tumbler::Tumbler(PP_Instance instance) + : pp::Instance(instance), + cube_(NULL) { +} + +Tumbler::~Tumbler() { + // Destroy the cube view while GL context is current. + opengl_context_->MakeContextCurrent(this); + delete cube_; +} + +bool Tumbler::Init(uint32_t /* argc */, + const char* /* argn */[], + const char* /* argv */[]) { + // Add all the methods to the scripting bridge. + ScriptingBridge::SharedMethodCallbackExecutor set_orientation_method( + new tumbler::MethodCallback<Tumbler>( + this, &Tumbler::SetCameraOrientation)); + scripting_bridge_.AddMethodNamed("setCameraOrientation", + set_orientation_method); + return true; +} + +void Tumbler::HandleMessage(const pp::Var& message) { + if (!message.is_string()) + return; + scripting_bridge_.InvokeMethod(message.AsString()); +} + +void Tumbler::DidChangeView(const pp::Rect& position, const pp::Rect& clip) { + int cube_width = cube_ ? cube_->width() : 0; + int cube_height = cube_ ? cube_->height() : 0; + if (position.size().width() == cube_width && + position.size().height() == cube_height) + return; // Size didn't change, no need to update anything. + + if (opengl_context_ == NULL) + opengl_context_.reset(new OpenGLContext(this)); + opengl_context_->InvalidateContext(this); + opengl_context_->ResizeContext(position.size()); + if (!opengl_context_->MakeContextCurrent(this)) + return; + if (cube_ == NULL) { + cube_ = new Cube(opengl_context_); + cube_->PrepareOpenGL(); + } + cube_->Resize(position.size().width(), position.size().height()); + DrawSelf(); +} + +void Tumbler::DrawSelf() { + if (cube_ == NULL || opengl_context_ == NULL) + return; + opengl_context_->MakeContextCurrent(this); + cube_->Draw(); + opengl_context_->FlushContext(); +} + +void Tumbler::SetCameraOrientation( + const tumbler::ScriptingBridge& bridge, + const tumbler::MethodParameter& parameters) { + // |parameters| is expected to contain one object named "orientation", whose + // value is a JSON string that represents an array of four floats. + if (parameters.size() != 1 || cube_ == NULL) + return; + std::string orientation_desc = GetParameterNamed("orientation", parameters); + std::vector<float> orientation = CreateArrayFromJSON(orientation_desc); + if (orientation.size() != kQuaternionElementCount) { + return; + } + cube_->SetOrientation(orientation); + DrawSelf(); +} +} // namespace tumbler + diff --git a/native_client_sdk/src/examples/tumbler/tumbler.h b/native_client_sdk/src/examples/tumbler/tumbler.h new file mode 100644 index 0000000..1738ec3 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/tumbler.h @@ -0,0 +1,64 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXAMPLES_TUMBLER_TUMBLER_H_ +#define EXAMPLES_TUMBLER_TUMBLER_H_ + +#include <pthread.h> +#include <map> +#include <vector> + +#include "examples/tumbler/cube.h" +#include "examples/tumbler/opengl_context.h" +#include "examples/tumbler/opengl_context_ptrs.h" +#include "examples/tumbler/scripting_bridge.h" +#include "ppapi/cpp/instance.h" + +namespace tumbler { + +class Tumbler : public pp::Instance { + public: + explicit Tumbler(PP_Instance instance); + + // The dtor makes the 3D context current before deleting the cube view, then + // destroys the 3D context both in the module and in the browser. + virtual ~Tumbler(); + + // Called by the browser when the NaCl module is loaded and all ready to go. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + // Called whenever the in-browser window changes size. + virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip); + + // Called by the browser to handle the postMessage() call in Javascript. + virtual void HandleMessage(const pp::Var& message); + + // Bind and publish the module's methods to JavaScript. + void InitializeMethods(ScriptingBridge* bridge); + + // Set the camera orientation to the quaternion in |args[0]|. |args| must + // have length at least 1; the first element is expeted to be an Array + // object containing 4 floating point number elements (the quaternion). + // This method is bound to the JavaScript "setCameraOrientation" method and + // is called like this: + // module.setCameraOrientation([0.0, 1.0, 0.0, 0.0]); + void SetCameraOrientation( + const tumbler::ScriptingBridge& bridge, + const tumbler::MethodParameter& parameters); + + // Called to draw the contents of the module's browser area. + void DrawSelf(); + + private: + // Browser connectivity and scripting support. + ScriptingBridge scripting_bridge_; + + SharedOpenGLContext opengl_context_; + // Wouldn't it be awesome if we had boost::scoped_ptr<>? + Cube* cube_; +}; + +} // namespace tumbler + +#endif // EXAMPLES_TUMBLER_TUMBLER_H_ diff --git a/native_client_sdk/src/examples/tumbler/tumbler.html b/native_client_sdk/src/examples/tumbler/tumbler.html new file mode 100644 index 0000000..a3002da --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/tumbler.html @@ -0,0 +1,33 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> + <!-- + Copyright (c) 2011 The Native Client Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + <head> + <title>Interactive Cube Example</title> + <script type="text/javascript"> + // Provide the tumbler namespace + tumbler = {}; + </script> + <script type="text/javascript" src="bind.js"></script> + <script type="text/javascript" src="dragger.js"></script> + <script type="text/javascript" src="tumbler.js"></script> + <script type="text/javascript" src="vector3.js"></script> + <script type="text/javascript" src="trackball.js"></script> + </head> + <body id="bodyId"> + <h1>Interactive Cube Example</h1> + <p> + The Native Client module executed in this page draws a 3D cube + and allows you to rotate it using a virtual trackball method. + </p> + <div id="tumbler_view"></div> + <script type="text/javascript"> + tumbler.application = new tumbler.Application(); + tumbler.application.run('tumbler_view'); + </script> + </body> +</HTML> diff --git a/native_client_sdk/src/examples/tumbler/tumbler.js b/native_client_sdk/src/examples/tumbler/tumbler.js new file mode 100644 index 0000000..e8e42eb --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/tumbler.js @@ -0,0 +1,133 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview The tumbler Application object. This object instantiates a + * Trackball object and connects it to the element named |tumbler_content|. + * It also conditionally embeds a debuggable module or a release module into + * the |tumbler_content| element. + */ + +// Requires tumbler +// Requires tumbler.Dragger +// Requires tumbler.Trackball + +/** + * Constructor for the Application class. Use the run() method to populate + * the object with controllers and wire up the events. + * @constructor + */ +tumbler.Application = function() { + /** + * The native module for the application. This refers to the module loaded + * via the <embed> tag. + * @type {Element} + * @private + */ + this.module_ = null; + + /** + * The trackball object. + * @type {tumbler.Trackball} + * @private + */ + this.trackball_ = null; + + /** + * The mouse-drag event object. + * @type {tumbler.Dragger} + * @private + */ + this.dragger_ = null; + + /** + * The function objects that get attached as event handlers. These are + * cached so that they can be removed when they are no longer needed. + * @type {function} + * @private + */ + this.boundModuleDidLoad_ = null; +} + +/** + * The ids used for elements in the DOM. The Tumlber Application expects these + * elements to exist. + * @enum {string} + * @private + */ +tumbler.Application.DomIds_ = { + MODULE: 'tumbler', // The <embed> element representing the NaCl module + VIEW: 'tumbler_view' // The <div> containing the NaCl element. +} + +/** + * Called by the module loading function once the module has been loaded. + * @param {?Element} nativeModule The instance of the native module. + */ +tumbler.Application.prototype.moduleDidLoad = function() { + this.module_ = document.getElementById(tumbler.Application.DomIds_.MODULE); + // Unbind the load function. + this.boundModuleDidLoad_ = null; + + /** + * Set the camera orientation property on the NaCl module. + * @param {Array.<number>} orientation A 4-element array representing the + * camera orientation as a quaternion. + */ + this.module_.setCameraOrientation = function(orientation) { + var methodString = 'setCameraOrientation ' + + 'orientation:' + + JSON.stringify(orientation); + this.postMessage(methodString); + } + + this.trackball_ = new tumbler.Trackball(); + this.dragger_ = new tumbler.Dragger(this.module_); + this.dragger_.addDragListener(this.trackball_); +} + +/** + * Asserts that cond is true; issues an alert and throws an Error otherwise. + * @param {bool} cond The condition. + * @param {String} message The error message issued if cond is false. + */ +tumbler.Application.prototype.assert = function(cond, message) { + if (!cond) { + message = "Assertion failed: " + message; + alert(message); + throw new Error(message); + } +} + +/** + * The run() method starts and 'runs' the application. The trackball object + * is allocated and all the events get wired up. + * @param {?String} opt_contentDivName The id of a DOM element in which to + * embed the Native Client module. If unspecified, defaults to + * VIEW. The DOM element must exist. + */ +tumbler.Application.prototype.run = function(opt_contentDivName) { + contentDivName = opt_contentDivName || tumbler.Application.DomIds_.VIEW; + var contentDiv = document.getElementById(contentDivName); + this.assert(contentDiv, "Missing DOM element '" + contentDivName + "'"); + + // Note that the <EMBED> element is wrapped inside a <DIV>, which has a 'load' + // event listener attached. This method is used instead of attaching the + // 'load' event listener directly to the <EMBED> element to ensure that the + // listener is active before the NaCl module 'load' event fires. + this.boundModuleDidLoad_ = this.moduleDidLoad.bind(this); + contentDiv.addEventListener('load', this.boundModuleDidLoad_, true); + + // Load the published .nexe. This includes the 'nacl' attribute which + // shows how to load multi-architecture modules. Each entry in the "nexes" + // object in the .nmf manifest file is a key-value pair: the key is the + // runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired + // NaCl module. To load the debug versions of your .nexes, set the 'nacl' + // attribute to the _dbg.nmf version of the manifest file. + contentDiv.innerHTML = '<embed id="' + + tumbler.Application.DomIds_.MODULE + '" ' + + 'src=tumbler.nmf ' + + 'type="application/x-nacl" ' + + 'width="480" height="480" />' +} diff --git a/native_client_sdk/src/examples/tumbler/tumbler_module.cc b/native_client_sdk/src/examples/tumbler/tumbler_module.cc new file mode 100644 index 0000000..932e564 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/tumbler_module.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "examples/tumbler/tumbler.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/gles2/gl2ext_ppapi.h" + +/// The Module class. The browser calls the CreateInstance() method to create +/// an instance of your NaCl module on the web page. The browser creates a new +/// instance for each <embed> tag with type="application/x-nacl". +class TumberModule : public pp::Module { + public: + TumberModule() : pp::Module() {} + virtual ~TumberModule() { + glTerminatePPAPI(); + } + + /// Called by the browser when the module is first loaded and ready to run. + /// This is called once per module, not once per instance of the module on + /// the page. + virtual bool Init() { + return glInitializePPAPI(get_browser_interface()) == GL_TRUE; + } + + /// Create and return a Tumbler instance object. + /// @param[in] instance The browser-side instance. + /// @return the plugin-side instance. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new tumbler::Tumbler(instance); + } +}; + +namespace pp { +/// Factory function called by the browser when the module is first loaded. +/// The browser keeps a singleton of this module. It calls the +/// CreateInstance() method on the object you return to make instances. There +/// is one instance per <embed> tag on the page. This is the main binding +/// point for your NaCl module with the browser. +Module* CreateModule() { + return new TumberModule(); +} +} // namespace pp + diff --git a/native_client_sdk/src/examples/tumbler/vector3.js b/native_client_sdk/src/examples/tumbler/vector3.js new file mode 100644 index 0000000..a79f781 --- /dev/null +++ b/native_client_sdk/src/examples/tumbler/vector3.js @@ -0,0 +1,91 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview A 3D vector class. Proviudes some utility functions on + * 3-dimentional vectors. + */ + +// Requires tumbler + +/** + * Constructor for the Vector3 object. This class contains a 3-tuple that + * represents a vector in 3D space. + * @param {?number} opt_x The x-coordinate for this vector. If null or + * undefined, the x-coordinate value is set to 0. + * @param {?number} opt_y The y-coordinate for this vector. If null or + * undefined, the y-coordinate value is set to 0. + * @param {?number} opt_z The z-coordinate for this vector. If null or + * undefined, the z-coordinate value is set to 0. + * @constructor + */ +tumbler.Vector3 = function(opt_x, opt_y, opt_z) { + /** + * The vector's 3-tuple. + * @type {number} + */ + this.x = opt_x || 0; + this.y = opt_y || 0; + this.z = opt_z || 0; +} + +/** + * Method to return the magnitude of a Vector3. + * @return {number} the magnitude of the vector. + */ +tumbler.Vector3.prototype.magnitude = function() { + return Math.sqrt(this.dot(this)); +} + +/** + * Normalize the vector in-place. + * @return {number} the magnitude of the vector. + */ +tumbler.Vector3.prototype.normalize = function() { + var mag = this.magnitude(); + if (mag < tumbler.Vector3.DOUBLE_EPSILON) + return 0.0; // |this| is equivalent to the 0-vector, don't normalize. + this.scale(1.0 / mag); + return mag; +} + +/** + * Scale the vector in-place by |s|. + * @param {!number} s The scale factor. + */ +tumbler.Vector3.prototype.scale = function(s) { + this.x *= s; + this.y *= s; + this.z *= s; +} + +/** + * Compute the dot product: |this| . v. + * @param {!tumbler.Vector3} v The vector to dot. + * @return {number} the result of |this| . v. + */ +tumbler.Vector3.prototype.dot = function(v) { + return this.x * v.x + this.y * v.y + this.z * v.z; +} + +/** + * Compute the cross product: |this| X v. + * @param {!tumbler.Vector3} v The vector to cross with. + * @return {tumbler.Vector3} the result of |this| X v. + */ +tumbler.Vector3.prototype.cross = function(v) { + var vCross = new tumbler.Vector3(this.y * v.z - this.z * v.y, + this.z * v.x - this.x * v.z, + this.x * v.y - this.y * v.x); + return vCross; +} + +/** + * Real numbers that are less than this distance apart are considered + * equivalent. + * TODO(dspringer): It seems as though there should be a const like this + * in generally available somewhere. + * @type {number} + */ +tumbler.Vector3.DOUBLE_EPSILON = 1.0e-16; diff --git a/native_client_sdk/src/libraries/build.scons b/native_client_sdk/src/libraries/build.scons new file mode 100644 index 0000000..abc1abc --- /dev/null +++ b/native_client_sdk/src/libraries/build.scons @@ -0,0 +1,41 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +Build file for the NaCl SDK Libraries + +This file runs all the scons files in the various libraries sub-directories. +Do not invoke this script directly, but instead use the scons or scons.bat +wrapper function. E.g. + +Linux or Mac: + ./scons [Options...] + +Windows: + scons.bat [Options...] +""" + +#------------------------------------------------------------------------------ +HELP_STRING = """ +=============================================================================== +Help for NaCl SDK Libraries +=============================================================================== + +* cleaning: ./scons -c +* build a target: ./scons <target> +* clean a target: ./scons -c <target> + +Supported targets: + * gtest_libs Build the gtest and gmock libraries. +""" + +libraries_sconscripts = [ + 'gtest/build.scons', + ] + +Help(HELP_STRING) + +SConscript(libraries_sconscripts) diff --git a/native_client_sdk/src/libraries/c_salt/test/gtest_event_listener.cc b/native_client_sdk/src/libraries/c_salt/test/gtest_event_listener.cc new file mode 100644 index 0000000..0afb3ed --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/test/gtest_event_listener.cc @@ -0,0 +1,100 @@ +// Copyright 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "c_salt/test/gtest_event_listener.h" + +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" + +namespace c_salt { + +GTestEventListener::GTestEventListener(pp::Instance* instance) + : instance_(instance), + factory_(this) { + assert(pp::Module::Get()->core()->IsMainThread()); +} + +void GTestEventListener::OnTestProgramStart( + const ::testing::UnitTest& unit_test) { + std::stringstream msg; + int num_tests = unit_test.test_to_run_count(); + int num_test_cases = unit_test.test_case_to_run_count(); + + msg << "::start::" << num_tests << " test"; + if (num_tests > 1) msg << 's'; + msg << " from "<< num_test_cases << " test case"; + if (num_test_cases > 1) msg << 's'; + msg << '.'; + PostMessage(msg.str()); +} + +void GTestEventListener::OnTestCaseStart( + const ::testing::TestCase& test_case) { + // not currently used +} + +void GTestEventListener::OnTestStart(const ::testing::TestInfo& test_info) { + // not currently used +} + +void GTestEventListener::OnTestPartResult( + const ::testing::TestPartResult& test_part_result) { + if (test_part_result.failed()) { + std::stringstream msg; + msg << "::test_failed::"; + msg << test_part_result.file_name(); + msg << "::" << test_part_result.line_number() << "::"; + msg << test_part_result.summary(); + PostMessage(msg.str()); + + msg.str(""); + msg << "::failure_log::"; + msg << test_part_result.summary(); + PostMessage(msg.str()); + } +} + +void GTestEventListener::OnTestEnd(const ::testing::TestInfo& test_info) { + std::stringstream msg; + msg << "::test_end::"; + msg << test_info.test_case_name() << "." << test_info.name(); + + msg << (test_info.result()->Failed() ? ": FAILED" : ": OK"); + PostMessage(msg.str()); +} + +void GTestEventListener::OnTestCaseEnd( + const ::testing::TestCase& test_case) { + // not used +} + +void GTestEventListener::OnTestProgramEnd( + const ::testing::UnitTest& unit_test) { + // Print info about what test and test cases ran. + int num_passed_tests = unit_test.successful_test_count(); + int num_failed_tests = unit_test.failed_test_count(); + std::stringstream msg; + msg << "::Result::"; + msg << ((num_failed_tests > 0) ? "failed::" : "success::"); + msg << num_passed_tests << "::" << num_failed_tests << "::"; + PostMessage(msg.str()); +} + +void GTestEventListener::PostMessage(const std::string& str) { + if (pp::Module::Get()->core()->IsMainThread()) { + instance_->PostMessage(str); + } else { + pp::CompletionCallback cc = factory_.NewCallback( + >estEventListener::PostMessageCallback, str); + pp::Module::Get()->core()->CallOnMainThread(0, cc, PP_OK); + } +} + +void GTestEventListener::PostMessageCallback(int32_t result, + const std::string& str) { + instance_->PostMessage(str); +} + +} // namespace c_salt + diff --git a/native_client_sdk/src/libraries/c_salt/test/gtest_event_listener.h b/native_client_sdk/src/libraries/c_salt/test/gtest_event_listener.h new file mode 100644 index 0000000..6419683 --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/test/gtest_event_listener.h @@ -0,0 +1,51 @@ +// Copyright 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef C_SALT_TEST_GTEST_EVENT_LISTENER_H_ +#define C_SALT_TEST_GTEST_EVENT_LISTENER_H_ + +#include <string> +#include "c_salt/threading/ref_count.h" +#include "gtest/gtest.h" +#include "ppapi/cpp/completion_callback.h" + +namespace pp { +class Instance; +} // namespace pp + +namespace c_salt { + +// GTestEventListener is a gtest event listener that performs two functions: +// 1. It redirects output to PostMessage rather than printf, so that test events +// are dispatched to JS. +// 2. Channels post-message dispatches to the main thread via callbacks, so that +// it can be used from any thread. +// +// GTestEventListener formats gtest event messages to be parsed by javascript +// helper functions found in gtest_runner.js +class GTestEventListener : public ::testing::EmptyTestEventListener { + public: + explicit GTestEventListener(pp::Instance* instance); + + // TestEventListener overrides. + virtual void OnTestProgramStart(const ::testing::UnitTest& unit_test); + virtual void OnTestCaseStart(const ::testing::TestCase& test_case); + virtual void OnTestStart(const ::testing::TestInfo& test_info); + virtual void OnTestPartResult( + const ::testing::TestPartResult& test_part_result); + virtual void OnTestEnd(const ::testing::TestInfo& test_info); + virtual void OnTestCaseEnd(const ::testing::TestCase& test_case); + virtual void OnTestProgramEnd(const ::testing::UnitTest& unit_test); + + private: + void PostMessage(const std::string& str); + void PostMessageCallback(int32_t result, const std::string& str); + + pp::Instance* instance_; + pp::CompletionCallbackFactory<GTestEventListener, + ::threading::RefCount> factory_; +}; + +} // namespace c_salt +#endif // C_SALT_TEST_GTEST_EVENT_LISTENER_H_ + diff --git a/native_client_sdk/src/libraries/c_salt/test/gtest_instance.cc b/native_client_sdk/src/libraries/c_salt/test/gtest_instance.cc new file mode 100644 index 0000000..1445bd9 --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/test/gtest_instance.cc @@ -0,0 +1,46 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "c_salt/test/gtest_instance.h" +#include "c_salt/test/gtest_runner.h" +#include "ppapi/cpp/var.h" + +namespace c_salt { + +GTestInstance::GTestInstance(PP_Instance instance) + : pp::Instance(instance) { +} + +GTestInstance::~GTestInstance() { +} + +bool GTestInstance::Init(uint32_t /* argc */, const char* /* argn */[], + const char* /* argv */[]) { + // Create a GTestRunner thread/singleton. + int local_argc = 0; + c_salt::GTestRunner::CreateGTestRunnerThread(this, local_argc, NULL); + return true; +} + +void GTestInstance::HandleMessage(const pp::Var& var_message) { + if (!var_message.is_string()) { + PostMessage("Invalid message"); + return; + } + + std::string message = var_message.AsString(); + if (message == "RunGTest") { + // This is our signal to start running the tests. Results from the tests + // are posted through GTestEventListener. + c_salt::GTestRunner::gtest_runner()->RunAllTests(); + } else { + std::string return_var; + return_var = "Unknown message "; + return_var += message; + PostMessage(return_var); + } +} + +} // namespace c_salt + diff --git a/native_client_sdk/src/libraries/c_salt/test/gtest_instance.h b/native_client_sdk/src/libraries/c_salt/test/gtest_instance.h new file mode 100644 index 0000000..aa180f8 --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/test/gtest_instance.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef C_SALT_TEST_GTEST_INSTANCE_H_ +#define C_SALT_TEST_GTEST_INSTANCE_H_ + +#include "ppapi/cpp/instance.h" + +namespace c_salt { + +// GTestInstance is a NaCl instance specifically dedicated to running +// gtest-based unit tests. It creates a GTestRunner thread/singleton pair as +// part of its Init function and runs all registered gtests once it +// receives a 'RunGTest' message in its post-message handler. Results from the +// test are posted back to JS through GTestEventListener. +// +// All tests are run from a background thread and must be written accordingly. +// Although that may complicate the test code a little, it allows the tests +// to be synchronized. I.e. Each test is launched and the thread is blocked +// until the outcome is known and reported. +class GTestInstance : public pp::Instance { + public: + explicit GTestInstance(PP_Instance instance); + virtual ~GTestInstance(); + + // pp::Instance overrides. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]); + virtual void HandleMessage(const pp::Var& var_message); +}; + +} // namespace c_salt + +#endif // C_SALT_TEST_GTEST_INSTANCE_H_ + diff --git a/native_client_sdk/src/libraries/c_salt/test/gtest_module.cc b/native_client_sdk/src/libraries/c_salt/test/gtest_module.cc new file mode 100644 index 0000000..3cc0ad0 --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/test/gtest_module.cc @@ -0,0 +1,28 @@ +// Copyright 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "c_salt/test/gtest_instance.h" +#include "ppapi/cpp/module.h" + +namespace c_salt { + +// GTestModule is a NaCl module dedicated to running gtest-based unit tests. +// It creates an NaCl instance based on GTestInstance. +class GTestModule : public pp::Module { + public: + GTestModule() : pp::Module() {} + ~GTestModule() {} + + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new c_salt::GTestInstance(instance); + } +}; + +} // namespace c_salt + +namespace pp { +Module* CreateModule() { + return new c_salt::GTestModule(); +} +} // namespace pp + diff --git a/native_client_sdk/src/libraries/c_salt/test/gtest_nacl_environment.cc b/native_client_sdk/src/libraries/c_salt/test/gtest_nacl_environment.cc new file mode 100644 index 0000000..416226b --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/test/gtest_nacl_environment.cc @@ -0,0 +1,19 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "c_salt/test/gtest_nacl_environment.h" + +namespace c_salt { + +pp::Instance* GTestNaclEnvironment::global_instance_ = NULL; + +void GTestNaclEnvironment::SetUp() { + // Check that the global instance is set before running the tests. + assert(global_instance_ != NULL); +} + +void GTestNaclEnvironment::TearDown() { +} + +} // namespace c_salt + diff --git a/native_client_sdk/src/libraries/c_salt/test/gtest_nacl_environment.h b/native_client_sdk/src/libraries/c_salt/test/gtest_nacl_environment.h new file mode 100644 index 0000000..8183565 --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/test/gtest_nacl_environment.h @@ -0,0 +1,39 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef C_SALT_TEST_GTEST_NACL_ENVIRONMENT_H_ +#define C_SALT_TEST_GTEST_NACL_ENVIRONMENT_H_ + +#include <cassert> +#include "gtest/gtest.h" + +namespace pp { +class Instance; +} // namespace pp + +namespace c_salt { + +// A custom environment for NaCl gtest. +class GTestNaclEnvironment : public ::testing::Environment { + public: + // Set the global NaCl instance that will be shared by all the tests. + static void set_global_instance(pp::Instance* instance) { + global_instance_ = instance; + } + + // Get the global NaCl instance. + static pp::Instance* global_instance() { return global_instance_; } + + // Environment overrides. + virtual void SetUp(); + virtual void TearDown(); + + private: + static pp::Instance* global_instance_; +}; + +} // namespace c_salt + +#endif // C_SALT_TEST_GTEST_NACL_ENVIRONMENT_H_ + diff --git a/native_client_sdk/src/libraries/c_salt/test/gtest_runner.cc b/native_client_sdk/src/libraries/c_salt/test/gtest_runner.cc new file mode 100644 index 0000000..d6afab0 --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/test/gtest_runner.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "c_salt/test/gtest_runner.h" + +#include <cassert> + +#include "c_salt/test/gtest_event_listener.h" +#include "c_salt/test/gtest_nacl_environment.h" +#include "gtest/gtest.h" + +namespace c_salt { + +pthread_t GTestRunner::g_test_runner_thread_ = NACL_PTHREAD_ILLEGAL_THREAD_ID; +GTestRunner* GTestRunner::gtest_runner_ = NULL; + +void GTestRunner::CreateGTestRunnerThread(pp::Instance* instance, + int argc, char** argv) { + assert(g_test_runner_thread_ == NACL_PTHREAD_ILLEGAL_THREAD_ID); + if (g_test_runner_thread_ == NACL_PTHREAD_ILLEGAL_THREAD_ID) { + gtest_runner_ = new GTestRunner(); + gtest_runner_->Init(instance, argc, argv); + pthread_create(&g_test_runner_thread_, NULL, ThreadFunc, NULL); + } +} + +void GTestRunner::RunAllTests() { + status_signal_.Lock(); + assert(status_ == kIdle); + status_ = kRunTests; + status_signal_.Signal(); + status_signal_.Unlock(); +} + +void* GTestRunner::ThreadFunc(void* param) { + gtest_runner_->RunLoop(); + delete gtest_runner_; + gtest_runner_ = NULL; + pthread_exit(NULL); + return NULL; +} + +GTestRunner::GTestRunner() : status_(kIdle) { } + +void GTestRunner::Init(pp::Instance* instance, int argc, char** argv) { + // Init gtest. + testing::InitGoogleTest(&argc, argv); + + // Add GTestEventListener to the list of listeners. That will cause the + // test output to go to nacltest.js through PostMessage. + ::testing::TestEventListeners& listeners = + ::testing::UnitTest::GetInstance()->listeners(); + delete listeners.Release(listeners.default_result_printer()); + listeners.Append(new c_salt::GTestEventListener(instance)); + + // We use our own gtest environment, mainly to make the nacl instance + // available to the individual unit tests. + c_salt::GTestNaclEnvironment* test_env = new c_salt::GTestNaclEnvironment(); + test_env->set_global_instance(instance); + ::testing::AddGlobalTestEnvironment(test_env); +} + +void GTestRunner::RunLoop() { + // Stay idle until RunAlltests is called. + status_signal_.Lock(); + while (status_ == kIdle) { + status_signal_.Wait(); + } + + assert(status_ == kRunTests); + status_signal_.Unlock(); + RUN_ALL_TESTS(); +} + +} // namespace c_salt + diff --git a/native_client_sdk/src/libraries/c_salt/test/gtest_runner.h b/native_client_sdk/src/libraries/c_salt/test/gtest_runner.h new file mode 100644 index 0000000..d8c2a6e --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/test/gtest_runner.h @@ -0,0 +1,62 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef C_SALT_TEST_GTEST_RUNNER_H_ +#define C_SALT_TEST_GTEST_RUNNER_H_ + +#include "c_salt/threading/pthread_ext.h" +#include "c_salt/threading/thread_condition.h" + +namespace pp { +class Instance; +} // namespace pp + +namespace c_salt { + +// GTestRunner is a threaded singleton for running gtest-based unit tests. +class GTestRunner { + public: + // Factory function to create the background gtest thread and associated + // GTestRunner singleton. It is an error to call the factory function more + // than once that raises an assert in debug mde. + // + // Parameters: + // instance: the NaCl instance. + // argc: arg count from pp::Instance::Init. + // argv: the arguments from pp::Instance::Init. + static void CreateGTestRunnerThread(pp::Instance* instance, + int argc, char** argv); + + // Get the GTestRunner singleton. + // @return A pointer to the GTestRunner singleton. + static GTestRunner* gtest_runner() { return gtest_runner_; } + + // Tell the GTestRunner to run all gtest unit tests. + void RunAllTests(); + + protected: + // The pthread thread function. + static void* ThreadFunc(void* param); + + // Don't try to create instances directly. Use the factory function instead. + GTestRunner(); + void Init(pp::Instance* instance, int argc, char** argv); + + // The thread run loop exits once all test have run. + void RunLoop(); + + private: + // The status and associated status signal are used to control the state of + // the thread run loop. + enum Status { kIdle, kRunTests }; + c_salt::threading::ThreadCondition status_signal_; + Status status_; + + static pthread_t g_test_runner_thread_; + static GTestRunner* gtest_runner_; +}; + +} // namespace c_salt + +#endif // C_SALT_TEST_GTEST_RUNNER_H_ + diff --git a/native_client_sdk/src/libraries/c_salt/threading/condition_lock.h b/native_client_sdk/src/libraries/c_salt/threading/condition_lock.h new file mode 100644 index 0000000..1d6b962 --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/threading/condition_lock.h @@ -0,0 +1,89 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONDITION_LOCK_H_ +#define CONDITION_LOCK_H_ + +#include <pthread.h> + +namespace threading { +// Class to manage a lock associated with a specific value. The calling thread +// can ask to acquire the lock only when the lock is in a certain condition. +class ConditionLock { + public: + ConditionLock() : condition_value_(0) { + InitLock(); + } + explicit ConditionLock(int32_t condition_value) + : condition_value_(condition_value) { + InitLock(); + } + + virtual ~ConditionLock() { + pthread_cond_destroy(&condition_condition_); + pthread_mutex_destroy(&condition_lock_); + } + + // Acquire the mutex without regard to the condition. + void Lock() { + pthread_mutex_lock(&condition_lock_); + } + + // Acquire the mutex lock when the lock values are equal. Blocks the + // calling thread until the lock can be acquired and the condition is met. + void LockWhenCondition(int32_t condition_value) { + Lock(); + while (condition_value != condition_value_) { + pthread_cond_wait(&condition_condition_, &condition_lock_); + } + // When this method returns, |contition_lock_| will be acquired. The + // calling thread must unlock it. + } + + // Acquire the mutex lock when the lock values are _NOT_ equal. Blocks the + // calling thread until the lock can be acquired and the condition is met. + void LockWhenNotCondition(int32_t condition_value) { + Lock(); + while (condition_value == condition_value_) { + pthread_cond_wait(&condition_condition_, &condition_lock_); + } + // When this method returns, |contition_lock_| will be acquired. The + // calling thread must unlock it. + } + + // Release the lock without changing the condition. Signal the condition + // so that threads waiting in LockWhenCondtion() will wake up. If there are + // no threads waiting for the signal, this has the same effect as a simple + // mutex unlock. + void Unlock() { + pthread_cond_broadcast(&condition_condition_); + pthread_mutex_unlock(&condition_lock_); + } + + // Release the lock, setting the condition's value. This assumes that + // |condition_lock_| has been acquired. + void UnlockWithCondition(unsigned int condition_value) { + condition_value_ = condition_value; + Unlock(); + } + + // Return the current condition value without any mutex protection. + int32_t condition_value() const { + return condition_value_; + } + + private: + void InitLock() { + pthread_mutex_init(&condition_lock_, NULL); + pthread_cond_init(&condition_condition_, NULL); + } + + pthread_mutex_t condition_lock_; + pthread_cond_t condition_condition_; + int32_t condition_value_; +}; +} // namespace threading + +#endif // CONDITION_LOCK_H_ + diff --git a/native_client_sdk/src/libraries/c_salt/threading/pthread_ext.h b/native_client_sdk/src/libraries/c_salt/threading/pthread_ext.h new file mode 100644 index 0000000..2152549 --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/threading/pthread_ext.h @@ -0,0 +1,15 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PTHREAD_EXT_H_ +#define PTHREAD_EXT_H_ + +// Include wrapper on pthread.h, with a few handy constants. + +#include <pthread.h> + +#define PTHREAD_MUTEX_SUCCESS 0 + +#endif // PTHREAD_EXT_H_ + diff --git a/native_client_sdk/src/libraries/c_salt/threading/ref_count.h b/native_client_sdk/src/libraries/c_salt/threading/ref_count.h new file mode 100644 index 0000000..55d3db1 --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/threading/ref_count.h @@ -0,0 +1,46 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef REF_COUNT_H_ +#define REF_COUNT_H_ + +#include "c_salt/threading/pthread_ext.h" + +namespace threading { + +// A thread-safe reference counter for class CompletionCallbackFactory. +class RefCount { + public: + RefCount() : ref_(0) { + pthread_mutex_init(&mutex_, NULL); + } + ~RefCount() { + pthread_mutex_destroy(&mutex_); + } + + int32_t AddRef() { + int32_t ret_val = 0; + if (pthread_mutex_lock(&mutex_) == PTHREAD_MUTEX_SUCCESS) { + ret_val = ++ref_; + pthread_mutex_unlock(&mutex_); + } + return ret_val; + } + + int32_t Release() { + int32_t ret_val = -1; + if (pthread_mutex_lock(&mutex_) == PTHREAD_MUTEX_SUCCESS) { + ret_val = --ref_; + pthread_mutex_unlock(&mutex_); + } + return ret_val; + } + + private: + int32_t ref_; + pthread_mutex_t mutex_; +}; + +} // namespace threading +#endif // REF_COUNT_H_ + diff --git a/native_client_sdk/src/libraries/c_salt/threading/scoped_mutex_lock.h b/native_client_sdk/src/libraries/c_salt/threading/scoped_mutex_lock.h new file mode 100644 index 0000000..03868ae --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/threading/scoped_mutex_lock.h @@ -0,0 +1,32 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SCOPED_MUTEX_LOCK_H_ +#define SCOPED_MUTEX_LOCK_H_ + +#include "c_salt/threading/pthread_ext.h" + +namespace threading { +// A small helper RAII class that implements a scoped pthread_mutex lock. +class ScopedMutexLock { + public: + explicit ScopedMutexLock(pthread_mutex_t* mutex) : mutex_(mutex) { + if (pthread_mutex_lock(mutex_) != PTHREAD_MUTEX_SUCCESS) { + mutex_ = NULL; + } + } + ~ScopedMutexLock() { + if (mutex_) + pthread_mutex_unlock(mutex_); + } + bool is_valid() const { + return mutex_ != NULL; + } + private: + pthread_mutex_t* mutex_; // Weak reference. +}; +} // namespace threading + +#endif // SCOPED_MUTEX_LOCK_H_ + diff --git a/native_client_sdk/src/libraries/c_salt/threading/thread_condition.h b/native_client_sdk/src/libraries/c_salt/threading/thread_condition.h new file mode 100644 index 0000000..cacd4dae --- /dev/null +++ b/native_client_sdk/src/libraries/c_salt/threading/thread_condition.h @@ -0,0 +1,76 @@ +// Copyright (c) 2011 The Native Client Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THREAD_CONDITION_H_ +#define THREAD_CONDITION_H_ + +#include "c_salt/threading/pthread_ext.h" + +struct timespec; + +namespace c_salt { +namespace threading { +// A wrapper class for condition signaling. Contains a mutex and condition +// pair. +class ThreadCondition { + public: + // Initialize the mutex and the condition. + ThreadCondition() { + pthread_mutex_init(&cond_mutex_, NULL); + pthread_cond_init(&condition_, NULL); + } + + virtual ~ThreadCondition() { + pthread_cond_destroy(&condition_); + pthread_mutex_destroy(&cond_mutex_); + } + + // Lock the mutex, do this before signalling the condition. + void Lock() { + pthread_mutex_lock(&cond_mutex_); + } + + // Unlock the mutex, do this after raising a signal or after returning from + // Wait(). + void Unlock() { + pthread_mutex_unlock(&cond_mutex_); + } + + // Signal the condition. This will cause Wait() to return. + void Signal() { + pthread_cond_broadcast(&condition_); + } + + // Wait for a Signal(). Note that this can spuriously return, so you should + // have a guard bool to see if the condtion is really true. E.g., in the + // calling thread: + // cond_lock->Lock(); + // cond_true = true; + // cond_lock->Signal(); + // cond_lock->Unlock(); + // In the worker thread: + // cond_lock->Lock(); + // while (!cond_true) { + // cond_lock->Wait(); + // } + // cond_lock->Unlock(); + void Wait() { + pthread_cond_wait(&condition_, &cond_mutex_); + } + + // Same as Wait, but wait at most until abs_time. Returns false if the system + // time exceeds abs_time before the condition is signaled. + bool TimedWait(struct timespec *abs_time) { + return (pthread_cond_timedwait(&condition_, &cond_mutex_, abs_time) == 0); + } + + private: + pthread_mutex_t cond_mutex_; + pthread_cond_t condition_; +}; +} // namespace threading +} // namespace c_salt + +#endif // THREAD_CONDITION_H_ + diff --git a/native_client_sdk/src/libraries/gtest/build.scons b/native_client_sdk/src/libraries/gtest/build.scons new file mode 100644 index 0000000..55a124e --- /dev/null +++ b/native_client_sdk/src/libraries/gtest/build.scons @@ -0,0 +1,112 @@ +# -*- python -*- +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import build_utils +import glob +import os +import shutil +import subprocess +import sys + +from SCons import Script + +# Directories used throughout this script. +script_dir = os.path.abspath(os.getcwd()) +sdk_root_dir = os.getenv('NACL_SDK_ROOT') +build_tools_dir = os.path.join(sdk_root_dir, 'build_tools') +libraries_dir = os.path.join(sdk_root_dir, 'libraries') + +# Add the path to build_tools to the shell's python path. +shell_env = os.environ.copy() +python_paths = [build_tools_dir] +python_paths += [shell_env.get('PYTHONPATH', '')] +shell_env['PYTHONPATH'] = os.pathsep.join(python_paths) + +# Argv for the install-gtest python script. +script_argv = [ + '--toolchain=%s' % ( + build_utils.NormalizeToolchain(base_dir=sdk_root_dir, + arch='x86', + variant='glibc')), + '--toolchain=%s' % ( + build_utils.NormalizeToolchain(base_dir=sdk_root_dir, + arch='x86', + variant='newlib')), + '--working_dir=%s' % script_dir + ] + +# The scons build env. +build_env = Script.Environment().Clone() + +# Where the src for gtest and gmock will be found after running the install +# script. We keep them around as a sentinel, to indicate they they have been +# installed. (See BuildGTestLibs below.) +gtest_src = os.path.join(script_dir, 'gtest-1.5.0') +gmock_src = os.path.join(script_dir, 'gmock-1.5.0') + + +def BuildGTestLibs(env, target, source): + '''Build and install the gtest/gmock libraries. + + This invokes the gtest_install.py script in the build_tools directory. In turn + that scripts downloads, untar, patches and build the gtest/gmock libraries. + Finally, the libraries and related include files are copied to the toolchain. + + Args: + env: The construction Environment() that is building the examples. + target: The target that triggered this build. Not used. + source: The sources used for this build. Not used. + ''' + # If our sentinel, the gtest source is present, do not build. + if os.path.exists(gtest_src): + return + # Remove any old gmock source if still present. + shutil.rmtree(gmock_src, ignore_errors=True) + # Invoke the gtest install script. + script = os.path.join(build_tools_dir, 'install_gtest', 'install_gtest.py') + py_command = [sys.executable, script] + subprocess.check_call(py_command + script_argv, env=shell_env) + + # Clean up: remove left-over tgz files. + for f in glob.iglob(os.path.join(script_dir, '*.tgz')): + os.remove(f) + + +def CleanGTestLibs(env, target, suite_name): + '''Clean the gtest/gmock libraries sources. + + This does a partial clean up of the gtest/gmock projects. It removes the src + directories. However, the actual libraries and related includes in the + toolchains are not removed. It is however sufficient to trigger a full + rebuild of gtest/gmock. + + Args: + env: The construction Environment() that is building the examples. + target: The target that triggered this build. + suite_name: A suite name that should cause this target to be cleaned. + ''' + # Only do this in 'clean' mode. + if not build_env.GetOption('clean'): + return + # Only clean target if it's on the cmd line or it's a clean all. + clean_this = True + if len(COMMAND_LINE_TARGETS) > 0: + clean_this = False + for cl_target in COMMAND_LINE_TARGETS: + if cl_target == suite_name or cl_target == target: + clean_this = True + break + # Delete the src trees for gtest and gmock. + if clean_this: + shutil.rmtree(gmock_src, ignore_errors=True) + shutil.rmtree(gtest_src, ignore_errors=True) + + +gtest_libs_builder = build_env.Alias('gtest_libs', [], BuildGTestLibs) +build_env.AlwaysBuild(gtest_libs_builder) +CleanGTestLibs(build_env, 'gtest_libs', 'bot') + +# ---------------------------------------------------------------------------- +build_env.Default('gtest_libs') diff --git a/native_client_sdk/src/libraries/scons b/native_client_sdk/src/libraries/scons new file mode 100755 index 0000000..053e32d --- /dev/null +++ b/native_client_sdk/src/libraries/scons @@ -0,0 +1,38 @@ +#!/bin/bash +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +readonly SCRIPT_DIR="$(dirname "$0")" +readonly SCRIPT_DIR_ABS="$(cd "${SCRIPT_DIR}" ; pwd -P)" + +# NACL_SDK_ROOT must be set. +if [ x"${NACL_SDK_ROOT}"x == "xx" ] ; then + echo "Error: NACL_SDK_ROOT is not set." + exit 1; +fi + +# NACL_TARGET_PLATFORM is really the name of a folder with the base dir - +# usually NACL_SDK_ROOT - within which the toolchain for the target platform +# are found. +# Replace the platform with the name of your target platform. For example, to +# build applications that target the pepper_17 API, set +# NACL_TARGET_PLATFORM="pepper_17" +if [ x"${NACL_TARGET_PLATFORM}"x == "xx" ] ; then + export NACL_TARGET_PLATFORM="pepper_17" +fi + +readonly NACL_PLATFORM_DIR="${NACL_SDK_ROOT}/${NACL_TARGET_PLATFORM}" +readonly BASE_SCRIPT="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/script/scons" + +export SCONS_LIB_DIR="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/engine" +export PYTHONPATH="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/engine:${NACL_PLATFORM_DIR}/build_tools" +# We have to do this because scons overrides PYTHONPATH and does not preserve +# what is provided by the OS. The custom variable name won't be overwritten. +export PYMOX="${NACL_PLATFORM_DIR}/third_party/pymox" + +"${BASE_SCRIPT}" --file=build.scons \ + --site-dir="${NACL_PLATFORM_DIR}/build_tools/nacl_sdk_scons" \ + $* + diff --git a/native_client_sdk/src/libraries/scons.bat b/native_client_sdk/src/libraries/scons.bat new file mode 100755 index 0000000..889a757 --- /dev/null +++ b/native_client_sdk/src/libraries/scons.bat @@ -0,0 +1,43 @@ +@echo off + +:: Copyright (c) 2011 The Native Client Authors. All rights reserved. +:: Use of this source code is governed by a BSD-style license that can be +:: found in the LICENSE file. + +setlocal + +:: NACL_SDK_ROOT must be set. +if not defined NACL_SDK_ROOT ( + echo Error: NACL_SDK_ROOT is not set. + echo Please set NACL_SDK_ROOT to the full path of the Native Client SDK. + echo For example: + echo set NACL_SDK_ROOT=D:\nacl_sdk + goto end +) + +:: NACL_TARGET_PLATFORM is really the name of a folder with the base dir - +:: usually NACL_SDK_ROOT - within which the toolchain for the target platform +:: are found. +:: Replace the platform with the name of your target platform. For example, to +:: build applications that target the pepper_17 API, set +:: NACL_TARGET_PLATFORM=pepper_17 +if not defined NACL_TARGET_PLATFORM ( + set NACL_TARGET_PLATFORM=pepper_17 +) + +set NACL_PLATFORM_DIR=%NACL_SDK_ROOT%\%NACL_TARGET_PLATFORM% + +set SCONS_LIB_DIR=%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\engine +set PYTHONPATH=%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\engine;%NACL_PLATFORM_DIR%\build_tools + +:: We have to do this because scons overrides PYTHONPATH and does not preserve +:: what is provided by the OS. The custom variable name won't be overwritten. +set PYMOX=%NACL_PLATFORM_DIR%\third_party\pymox + +:: Run the included copy of scons. +python -O -OO "%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\script\scons" ^ +--warn no-visual-c-missing ^ +--file=build.scons ^ +--site-dir="%NACL_PLATFORM_DIR%\build_tools\nacl_sdk_scons" %* + +:end diff --git a/native_client_sdk/src/main.scons b/native_client_sdk/src/main.scons new file mode 100644 index 0000000..557333d --- /dev/null +++ b/native_client_sdk/src/main.scons @@ -0,0 +1,1065 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" Main scons script for Native Client SDK builds. + +Do not invoke this script directly, but instead use the scons or scons.bat +wrapper function. E.g. + +Linux or Mac: + ./scons [Options...] + +Windows: + scons.bat [Options...] +""" + +from __future__ import with_statement + +import os +import platform +import subprocess +import sys +import toolchainbinaries +from build_tools import build_utils + +# ---------------------------------------------------------------------------- +HELP_STRING = """ +=============================================================================== +Help for NaCl SDK +=============================================================================== + +* cleaning: ./scons -c +* build a target: ./scons <target> + +Supported targets: + * bot Runs everything that the build and try bots run. + * debug_server_x64 Build the out-of-process debug_server. + * debug_server_Win32 Build the out-of-process debug_server. + * docs Build all of the Doxygen documentation. + * examples Build the examples. + * experimental Build the experimental projects. + * installer Build the SDK installer. + * nacl-bpad Build the native client crash reporting tool. + * sdk_tools Build nacl_sdk.zip and sdk_tools.tgz + * toolchain Update the toolchain's headers and libraries. + * vsx Build the Visual Studio Plugin. + +Flags: + * USE_EXISTING_INSTALLER=1 Do not rebuild the installer if it exists. + * chrome_browser_path=<full_path> Download Chrome to <full_path>. + * SHOW_BROWSER=1 Don't suppress browser GUI while performing + browser tests in Linux. + +More targets are listed below in the automatically generated help section. + +=============================================================================== +Automatically generated help follows: +=============================================================================== +""" + +# ---------------------------------------------------------------------------- +# Perform some environment checks before running. +# Note that scons should set NACL_SDK_ROOT before this script runs. + +if os.getenv('NACL_SDK_ROOT') is None: + sys.stderr.write('NACL_SDK_ROOT must be defined as the root directory' + ' of NaCl SDK.\n') + sys.exit(1) + +# By default, run with a parallel build (i.e. '-j num_jobs'). +# Use a default value proportional to the number of cpu cores on the system. +# To run a serial build, explicitly type '-j 1' on the command line. +try: + import multiprocessing + CORE_COUNT = multiprocessing.cpu_count() +except (ImportError, NotImplementedError): + CORE_COUNT = 2 # Our buildbots seem to be dual-core typically + +SetOption('num_jobs', CORE_COUNT * 2) +print 'Building with', GetOption('num_jobs'), 'parallel jobs' + +# ---------------------------------------------------------------------------- +# The environment_list contains all the build environments that we want to +# specify. Selecting a particular environment is done using the --mode option. +# Each environment that we support gets appended to this list. +environment_list = [] + +# ---------------------------------------------------------------------------- +# Create the base environment, from which all other environments are derived. +base_env = Environment( + tools = ['component_setup'], + CPPPATH = ['$MAIN_DIR'], + CPPDEFINES = [ + 'BOOST_ALL_NO_LIB', + ], + NACL_TOOLCHAIN_ROOTS = { + ('x86', 'newlib'): + build_utils.NormalizeToolchain(arch='x86', variant='newlib'), + ('x86', 'glibc'): + build_utils.NormalizeToolchain(arch='x86', variant='glibc'), + }, + ROOT_DIR = os.path.abspath(os.getcwd()), + IS_WINDOWS = sys.platform in ['cygwin', 'win32'], + IS_LINUX = sys.platform == 'linux2', + IS_MAC = sys.platform == 'darwin', + JOB_COUNT = GetOption('num_jobs') +) + +# It is possible to override these values on the command line by typing +# something like this: +# PYTHON=/path/to/my/python +base_env.SetDefault( + PYTHON = ARGUMENTS.get('PYTHON', 'python'), + USE_EXISTING_INSTALLER = ARGUMENTS.get('USE_EXISTING_INSTALLER', False), + SHOW_BROWSER = ARGUMENTS.get('SHOW_BROWSER', False), +) + +base_env.Append( + BUILD_SCONSCRIPTS = [ + # Keep in alphabetical order + 'build_tools/build.scons', + 'debugger/build.scons', + 'documentation/build.scons', + 'experimental/visual_studio_plugin/build.scons', + 'experimental/webgtt/tests/nacltest/test.scons', + 'project_templates/test.scons', + ], +) + +base_env.Help(HELP_STRING) + +KNOWN_SUITES = frozenset([ + 'bot', + ]) + + +def HasBotTarget(env): + if 'bot' in COMMAND_LINE_TARGETS: + return True + return False + +base_env.AddMethod(HasBotTarget) + + +def CheckSuiteName(suite, node_name): + '''Check whether a given test suite or alias name is a known name. + + If the suite name is not in the approved list, then this function throws + an exception, with the node_name within the error message. + + Args: + suite: a name of a suite that must be in the KNOWN_SUITES set + node_name: The name of the node. This is used for error messages + ''' + if suite not in KNOWN_SUITES: + raise Exception('Testsuite/Alias "%s" for target "%s" is unknown' % + (suite, node_name)) + + +def AddNodeToTestSuite(env, node, suite_names, node_name, test_size='all'): + '''Adds a test node to a given set of suite names + + These tests are automatically added to the run_all_tests target and are + listed in the help screen. + + This function is loosely based on a function of the same name in the + Native Client repository + + Args: + env - The environment from which this function was called + node - A scons node (e.g., file, command, etc) to be added to set suite + suite_names - A list of test suite names. For none, pass an empty list + node_name - The target name used for running this test + test_size - The relative run-time of this test: small, medium, or large + ''' + + # CommandTest can return an empty list when it silently discards a test + if not node: + return + + AlwaysBuild(node) + + for s in suite_names: + CheckSuiteName(s, node_name) + env.Alias(s, node) + + if test_size not in ['small', 'medium', 'large', 'all']: + raise Exception('Invalid test size for %s' % node_name) + + # Note that COMPONENT_TEST_SIZE is set to 'large' by default, which + # populates a largely redundant list of 'large' tests. Note that all + # tests are added to 'all', so setting test_size='all' is a no-op + env.ComponentTestOutput(node_name, node, COMPONENT_TEST_SIZE=test_size) + +base_env.AddMethod(AddNodeToTestSuite) + + +def ShouldBeCleaned(env, targets, suite_names, node_name): + '''Determines whether a given set of targets require cleaning. + + Args: + env - The calling environment. + targets - Any build artifacts to which a cleaning step might apply. + Any false object indicates that this check is skipped. + suite_names - Any suites that might produce |targets| + node_name - A node that might produce |targets| + ''' + if not env.GetOption('clean'): + return False + + if len(COMMAND_LINE_TARGETS) > 0: + clean_this = False + for cl_target in COMMAND_LINE_TARGETS: + if cl_target in suite_names or cl_target == node_name: + clean_this = True + break + if not clean_this: + return False + + if not targets: + return True + for target in targets: + if os.path.exists(target): + return True + return False + + +def AddCleanAction(env, targets, action, suite_names, node_name): + '''Adds a cleanup action that scons cannot detect automatically. + + Cleaning will only occur if there is a match between the suite or nodes + specified on the command line, and suite_names or node_name or if no + suite or nodes are specified on the command line. Also, at least one of the + targets must exist on the file system. + + Args: + env - The calling environment + targets - Artifacts to be cleaned. + action - The action to be performed. It is up to the caller to ensure + that |action| will actually remove |targets| + suite_names - Any suites to which this cleanup target applies. + node_name - Any nodes to which this cleanup target applies. + ''' + if ShouldBeCleaned(env, targets, suite_names, node_name): + env.Execute(action) + +base_env.AddMethod(AddCleanAction) + + +def AddNodeAliases(env, node, suite_names, node_name): + '''Allow a given node to be built under a different name or as a suite + + Args: + env - The calling environment + node - A target node to add to a known build alias (e.g., 'bot') + suite_names - A list of suite names. For none, pass an empty list. This + node will be run whenever any of these suites are invoked. + Each suite name must match a string in KNOWN_SUITES. + node_name - The name of this node, when run by itself + ''' + + if not node: + return + + for s in suite_names: + CheckSuiteName(s, node_name) + env.Alias(s, node) + + env.Alias(node_name, node) + +base_env.AddMethod(AddNodeAliases) + + +def CreatePythonUnitTest(env, filename, dependencies=None, disabled=False, + params=None, buffered=True, banner=None): + """Returns a new build command that will run a unit test with a given file. + + Args: + env: SCons environment + filename: The python file that contains the unit test + dependencies: An optional list of other files that this unit test uses + disabled: Setting this to True will prevent the test from running + params: Optional additional parameters for python command + buffered: True=stdout is buffered until entirely complete; + False=stdout is immediately displayed as it occurs. + banner: (optional) annotation banner for build/try bots + + Returns: + A SCons command node + """ + dependencies = dependencies or [] + params = params or [] + + basename = os.path.splitext(os.path.basename(filename))[0] + outfilename = "%s_output.txt" % basename + + + def RunPythonUnitTest(env, target, source): + """Runs unit tests using the given target as a command. + + The argument names of this test are not very intuitive but match what is + used conventionally throughout scons. If the string "PASSED" does not + occur in target when this exits, the test has failed; also a scons + convention. + + Args: + env: SCons's current environment. + target: Where to write the result of the test. + source: The command to run as the test. + + Returns: + None for good status + An error string for bad status + """ + bot = build_utils.BotAnnotator() + if banner: + bot.BuildStep(banner) + + if disabled: + sys.stdout.write("Test %s is disabled.\n" % basename) + sys.stdout.flush() + return None # return with good status + + import subprocess + + app = [str(env['PYTHON']), str(source[0].abspath)] + map( + lambda param: param if type(param) is str else str(param.abspath), + params) + bot.Print('Running: %s' % app) + app_env = os.environ.copy() + # We have to do this because scons overrides PYTHONPATH and does + # not preserve what is provided by the OS. + python_path = [env['ROOT_DIR'], app_env['PYMOX'], app_env['PYTHONPATH']] + app_env['PYTHONPATH'] = os.pathsep.join(python_path) + ret_val = 'Error: General Test Failure' # Indicates failure, by default + target_str = str(target[0]) + with open(target_str, 'w') as outfile: + def Write(str): + if buffered: + outfile.write(str) + outfile.flush() + else: + sys.stdout.write(str) + sys.stdout.flush() + Write('\n-----Begin output for Test: %s\n' % basename) + if subprocess.call(app, env=app_env, + stdout=outfile if buffered else None, + stderr=outfile if buffered else None): + Write('-----Error: unit test failed\n') + ret_val = 'Error: Test Failure in %s' % basename + else: + ret_val = None # Indicates success + + Write('-----End output for Test: %s\n' % basename) + if buffered: + with open(target_str, 'r') as resultfile: + sys.stdout.write(resultfile.read()) + sys.stdout.flush() + + if ret_val: + bot.BuildStepFailure() + + return ret_val + + cmd = env.Command(outfilename, filename, RunPythonUnitTest) + env.Depends(cmd, dependencies) + # Create dependencies for all the env.File parameters and other scons nodes + for param in params: + if type(param) is not str: + env.Depends(cmd, param) + + return cmd + +base_env.AddMethod(CreatePythonUnitTest) + + +# ---------------------------------------------------------------------------- +# Support for running Chrome. These functions access the construction +# Environment() to produce a path to Chrome. + +# A Dir object representing the directory where the Chrome binaries are kept. +# You can use chrome_binaries_dir= to set this on the command line. Defaults +# to chrome_binaries. +base_env['CHROME_DOWNLOAD_DIR'] = \ + base_env.Dir(ARGUMENTS.get('chrome_binaries_dir', '#chrome_binaries')) + + +def ChromeArchitectureSpec(env): + '''Determine the architecture spec for the Chrome binary. + + The architecture spec is a string that represents the host architecture. + Possible values are: + x86-32 + x86-64 + On Mac and Windows, the architecture spec is always x86-32, because there are + no 64-bit version available. + + Returns: An architecture spec string for the host CPU. + ''' + arch, _ = platform.architecture(); + # On Mac and Windows, always use a 32-bit version of Chrome (64-bit versions + # are not available). + if env['IS_WINDOWS'] or env['IS_MAC']: + arch = 'x86-32' + else: + arch = 'x86-64' if '64' in arch else 'x86-32' + return arch + +base_env.AddMethod(ChromeArchitectureSpec) + + +def GetDefaultChromeBinary(env): + '''Get a File object that represents a Chrome binary. + + By default, the test infrastructure will download a copy of Chrome that can + be used for testing. This method returns a File object that represents the + downloaded Chrome binary that can be run by tests. Note that the path to the + binary is specific to the host platform, for example the path on Linux + is <chrome_dir>/linux/<arch>/chrome, while on Mac it's + <chrome_dir>/mac/<arch>/Chromium.app/Contents.MacOS/Chromium. + + Returns: A File object representing the Chrome binary. + ''' + if env['IS_LINUX']: + os_name = 'linux' + binary = 'chrome' + elif env['IS_WINDOWS']: + os_name = 'windows' + binary = 'chrome.exe' + elif env['IS_MAC']: + os_name = 'mac' + binary = 'Chromium.app/Contents/MacOS/Chromium' + else: + raise Exception('Unsupported OS') + + return env.File(os.path.join( + '${CHROME_DOWNLOAD_DIR}', + '%s_%s' % (os_name, env.ChromeArchitectureSpec()), + binary)) + +base_env.AddMethod(GetDefaultChromeBinary) + + +def GetChromeBinary(env): + '''Return a File object that represents the downloaded Chrome binary. + + If chrome_browser_path is specified on the command line, then return a File + object that represents that path. Otherwise, return a File object + representing the default downloaded Chrome (see GetDefaultChromeBinary(), + above). + + Returns: A File object representing a Chrome binary. + ''' + return env.File(ARGUMENTS.get('chrome_browser_path', + env.GetDefaultChromeBinary())) + +base_env.AddMethod(GetChromeBinary) + + +def DependsOnChrome(env, dependency): + '''Create a dependency on the download of Chrome. + + Creates a dependency in |env| such that Chrome gets downloaded (if necessary) + whenever |dependency| changes. Uses the Chrome downloader scripts built + into NaCl; this script expects NaCl to be DEPS'ed into + third_party/native_client/native_client. + + The Chrome binary is added as a precious node to the base Environment. If + we added it to the build environment env, then downloading chrome would in + effect be specified for multiple environments. + ''' + if not hasattr(base_env, 'download_chrome_node'): + chrome_binary = env.GetDefaultChromeBinary() + download_chrome_script = build_utils.JoinPathToNaClRepo( + 'native_client', 'build', 'download_chrome.py', + root_dir=env['ROOT_DIR']) + base_env.download_chrome_node = env.Command( + chrome_binary, + [], + '${PYTHON} %s --arch=%s --dst=${CHROME_DOWNLOAD_DIR}' % + (download_chrome_script, env.ChromeArchitectureSpec())) + # This stops Scons from deleting the file before running the step above. + env.NoClean(chrome_binary) + env.Precious(chrome_binary) + env.Depends(dependency, base_env.download_chrome_node) + +base_env.AddMethod(DependsOnChrome) + + +# ---------------------------------------------------------------------------- +# Targets for updating sdk headers and libraries +# NACL_SDK_XXX vars are defined by site_scons/site_tools/naclsdk.py +# NOTE: Our task here is complicated by the fact that there might already be +# some (outdated) headers/libraries at the new location +# One of the hacks we employ here is to make every library depend +# on the installation on ALL headers (sdk_headers) + +# Contains all the headers to be installed +sdk_headers = base_env.Alias('extra_sdk_update_header', []) +# Contains all the libraries and .o files to be installed +libs_platform = base_env.Alias('extra_sdk_libs_platform', []) +libs = base_env.Alias('extra_sdk_libs', []) +base_env.Alias('extra_sdk_update', [libs, libs_platform]) + +AlwaysBuild(sdk_headers) + +# ---------------------------------------------------------------------------- +# The following section contains proxy nodes which can be used to create +# dependencies between targets that are not in the same scope or environment. +toolchain_node = base_env.Alias('toolchain', []) + + +def GetToolchainNode(env): + '''Returns the node associated with the toolchain build target''' + return toolchain_node + +base_env.AddMethod(GetToolchainNode) + + +def GetHeadersNode(env): + return sdk_headers + +base_env.AddMethod(GetHeadersNode) + +installer_prereqs_node = base_env.Alias('installer_prereqs', []) + + +def GetInstallerPrereqsNode(env): + return installer_prereqs_node + +base_env.AddMethod(GetInstallerPrereqsNode) + +installer_test_node = base_env.Alias('installer_test_node', []) + + +def GetInstallerTestNode(env): + return installer_test_node + +base_env.AddMethod(GetInstallerTestNode) + + +def AddHeaderToSdk(env, nodes, subdir = 'nacl/', base_dirs = None): + """Add a header file to the toolchain. By default, Native Client-specific + headers go under nacl/, but there are non-specific headers, such as + the OpenGLES2 headers, that go under their own subdir. + + Args: + env: Environment in which we were called. + nodes: A list of node objects to add to the toolchain + subdir: This is appended to each base_dir + base_dirs: A list of directories to install the node to""" + if not base_dirs: + # TODO(mball): This won't work for PNaCl: + base_dirs = [os.path.join(dir, 'x86_64-nacl', 'include') + for dir in env['NACL_TOOLCHAIN_ROOTS'].values()] + + for base_dir in base_dirs: + node = env.Replicate(os.path.join(base_dir, subdir), nodes) + env.Depends(sdk_headers, node) + env.Depends(toolchain_node, node) + return node + +base_env.AddMethod(AddHeaderToSdk) + + +def AddLibraryToSdkHelper(env, nodes, is_lib, is_platform): + """"Helper function to install libs/objs into the toolchain + and associate the action with the extra_sdk_update. + + Args: + env: Environment in which we were called. + nodes: list of libc/objs + is_lib: treat nodes as libs + is_platform: nodes are truly platform specific + """ + env.Requires(nodes, sdk_headers) + + dir = ARGUMENTS.get('extra_sdk_lib_destination') + if not dir: + dir = '${NACL_SDK_LIB}/' + + if is_lib: + n = env.ReplicatePublished(dir, nodes, 'link') + else: + n = env.Replicate(dir, nodes) + + if is_platform: + env.Alias('extra_sdk_libs_platform', n) + else: + env.Alias('extra_sdk_libs', n) + return n + + +def AddLibraryToSdk(env, nodes, is_platform=False): + return AddLibraryToSdkHelper(env, nodes, True, is_platform) + +base_env.AddMethod(AddLibraryToSdk) + + +# ---------------------------------------------------------------------------- +# This is a simple environment that is primarily for targets that aren't built +# directly by scons, and therefore don't need any special environment setup. +build_env = base_env.Clone( + BUILD_TYPE = 'build', + BUILD_GROUPS = ['default', 'all'], + BUILD_TYPE_DESCRIPTION = 'Default build environment', + HOST_PLATFORMS = '*', + ) + +environment_list.append(build_env) + +# ---------------------------------------------------------------------------- +# Get the appropriate build command depending on the environment. + + +def SconsBuildCommand(env): + '''Return the build command used to run separate scons instances. + Args: + env: The construction Environment() that is building using scons. + Returns: + A string representing the platform-specific build command that will run the + scons instances. + ''' + if env['IS_WINDOWS']: + return 'scons.bat --jobs=%s' % GetOption('num_jobs') + else: + return './scons --jobs=%s' % GetOption('num_jobs') + +# ---------------------------------------------------------------------------- +# Add a builder for examples. This adds an Alias() node named 'examples' that +# is always built. There is some special handling for the clean mode, since +# SCons relies on actual build products for its clean processing and will not +# run Alias() actions during clean unless they actually produce something. + + +def BuildExamples(env, target, source): + '''Build the examples. + + This runs the build command in the 'examples' directory. + + Args: + env: The construction Environment() that is building the examples. + target: The target that triggered this build. Not used. + source: The sources used for this build. Not used. + ''' + os_env = os.environ.copy() + os_env['NACL_TARGET_PLATFORM'] = '.' + subprocess.check_call(SconsBuildCommand(env), + cwd='examples', + env=os_env, + shell=True) + + +def CleanExamples(env, target, source): + '''Clean the examples. + + This runs the clean command in the 'examples' directory. + + Args: + env: The construction Environment() that is building the examples. + ''' + os_env = os.environ.copy() + os_env['NACL_TARGET_PLATFORM'] = '.' + subprocess.check_call('%s --clean' % SconsBuildCommand(env), + cwd='examples', + env=os_env, + shell=True) + + +examples_builder = build_env.Alias('examples', [toolchain_node], BuildExamples) +build_env.AlwaysBuild(examples_builder) +build_env.AddCleanAction(['examples'], CleanExamples, ['bot'], + examples_builder) + + +def DependsOnExamples(env, dependency): + env.Depends(dependency, examples_builder) + +build_env.AddMethod(DependsOnExamples) + + +# ---------------------------------------------------------------------------- +# Add a builder for experimental projects. This adds an Alias() node named +# 'experimental' that is always built. There is some special handling for the +# clean mode, since SCons relies on actual build products for its clean +# processing and will not run Alias() actions during clean unless they actually +# produce something. + + +def BuildExperimental(env, target, source): + '''Build the experimental projects. + + This runs the build command in the 'experimental' directory. + + Args: + env: The construction Environment() that is building the experimental + projects. + target: The target that triggered this build. Not used. + source: The sources used for this build. Not used. + ''' + subprocess.check_call(SconsBuildCommand(env), + cwd='experimental', + shell=True) + + +def CleanExperimental(env, target, source): + '''Clean the experimental projects. + + This runs the clean command in the 'experimental' directory. + + Args: + env: The construction Environment() that is building the experimental + projects. + ''' + subprocess.check_call('%s --clean' % SconsBuildCommand(env), + cwd='experimental', + shell=True) + + +experimental_builder = build_env.Alias('experimental', [toolchain_node], + BuildExperimental) +build_env.AlwaysBuild(experimental_builder) +build_env.AddCleanAction(['experimental'], CleanExperimental, ['bot'], + experimental_builder) + +# ---------------------------------------------------------------------------- +# Add helper functions that build/clean a test by invoking scons under the +# test directory (|cwd|, when specified). These functions are meant to be +# called from corresponding project-specific 'Build<project>Test' and +# 'Clean<project>Test' functions in the local test.scons scripts. Note that the +# CleanNaClTest does not require a |cwd| because its cwd is always '.' + + +def BuildNaClTest(env, cwd): + '''Build the test. + + This runs the build command in the test directory from which it is called. + + Args: + env: The construction Environment() that is building the test. + cwd: The directory under which the test's build.scons rests. + ''' + subprocess.check_call('%s stage' % SconsBuildCommand(env), + cwd=cwd, + shell=True) + +build_env.AddMethod(BuildNaClTest) + + +def CleanNaClTest(env): + '''Clean the test. + + This runs the clean command in the test directory from which it is called. + + Args: + env: The construction Environment() that is building the test. + cwd: The directory under which the test's build.scons rests. + ''' + subprocess.check_call('%s stage --clean' % SconsBuildCommand(env), + shell=True) + # The step above still leaves behind two empty 'opt' directories, so a second + # cleaning pass is necessary. + subprocess.check_call('%s --clean' % SconsBuildCommand(env), + shell=True) + +build_env.AddMethod(CleanNaClTest) + +# ---------------------------------------------------------------------------- +# Enable PPAPIBrowserTester() functionality using nacltest.js +# NOTE: The three main functions in this section: PPAPIBrowserTester(), +# CommandTest(), and AutoDepsCommand() are 'LITE' versions of their counterparts +# provided by Native Client @ third_party/native_client/native_client/SConstruct + + +def SetupBrowserEnv(env): + '''Set up the environment for running the browser. + + This copies environment parameters provided by the OS in order to run the + browser reliably. + + Args: + env: The construction Environment() that runs the browser. + ''' + EXTRA_ENV = ['XAUTHORITY', 'HOME', 'DISPLAY', 'SSH_TTY', 'KRB5CCNAME'] + for var_name in EXTRA_ENV: + if var_name in os.environ: + env['ENV'][var_name] = os.environ[var_name] + + env.Append( + PYTHONPATH = [ + build_utils.JoinPathToNaClRepo( + 'third_party', 'pylib', + root_dir=os.getenv('NACL_SDK_ROOT')), + build_utils.JoinPathToNaClRepo( + 'tools', 'valgrind', + root_dir=os.getenv('NACL_SDK_ROOT')), + ] + ) + + +def PPAPIBrowserTester(env, + target, + url, + files, + timeout=20): + '''The main test wrapper for browser integration tests. + + This constructs the command that invokes the browser_tester.py script on an + existing Chrome binary (to be downloaded if necessary). + + Args: + env: The construction Environment() that runs the browser. + target: The output file to which the output of the test is to be written. + url: The test web page. + files: The files necessary for the web page to be served. + timeout: How long to wait for a response before concluding failure. + + Returns: A command node that executes the browser test. + ''' + + env = env.Clone() + SetupBrowserEnv(env) + + python_tester_script = build_utils.JoinPathToNaClRepo( + 'native_client', 'tools', 'browser_tester', 'browser_tester.py', + root_dir=env['ROOT_DIR']) + + # Check if browser GUI needs to be suppressed (possible only in Linux) + headless_prefix = [] + if not env['SHOW_BROWSER'] and env['IS_LINUX']: + headless_prefix = ['xvfb-run', '--auto-servernum'] + + command = headless_prefix + [ + '${PYTHON}', python_tester_script, + '--browser_path', env.GetChromeBinary(), + '--url', url, + # Fail if there is no response for X seconds. + '--timeout', str(timeout)] + + for dep_file in files: + command.extend(['--file', dep_file]) + + cmd = env.CommandTest(target, + command, + # Set to 'huge' so that the browser tester's timeout + # takes precedence over the default of the test suite. + size='huge', + capture_output=False) + env.DependsOnChrome(cmd) + + return cmd + +build_env.AddMethod(PPAPIBrowserTester) + + +def CommandTest(env, + name, + command, + size='small', + capture_output=True): + '''The wrapper for testing execution of a command and logging details. + + This constructs the command that invokes the command_tester.py script on a + given command. + + Args: + env: The construction Environment() that runs the command. + name: The output file to which the output of the tester is to be written. + command: The command to be tested. + size: This dictates certain timeout thresholds. + capture_output: This specifies whether the command's output needs to be + captured for further processing. When this option is False, + stdout and stderr will be streamed out. For more info, see + <NACL_REPO>/native_client/tools/command_tester.py + + Returns: A command node that executes the command test. + ''' + TEST_TIME_THRESHOLD = { + 'small': 2, + 'medium': 10, + 'large': 60, + 'huge': 1800, + } + + if not name.endswith('out') or name.startswith('$'): + raise Exception('ERROR: bad test filename for test output %r' % name) + + arch_string = env.ChromeArchitectureSpec(); + if env['IS_LINUX']: + platform_string = 'linux' + elif env['IS_MAC']: + platform_string = 'mac' + elif env['IS_WINDOWS']: + platform_string = 'windows' + + name = '${TARGET_ROOT}/test_results/' + name + max_time = TEST_TIME_THRESHOLD[size] + + script_flags = ['--name', name, + '--report', name, + '--time_warning', str(max_time), + '--time_error', str(10 * max_time), + '--perf_env_description', platform_string + '_' + arch_string, + '--arch', 'x86', + '--subarch', arch_string[-2:], + ] + if not capture_output: + script_flags.extend(['--capture_output', '0']) + + test_script = build_utils.JoinPathToNaClRepo( + 'native_client', 'tools', 'command_tester.py', + root_dir=env['ROOT_DIR']) + command = ['${PYTHON}', test_script] + script_flags + command + return AutoDepsCommand(env, name, command) + +build_env.AddMethod(CommandTest) + + +def AutoDepsCommand(env, name, command): + """AutoDepsCommand() takes a command as an array of arguments. Each + argument may either be: + + * a string, or + * a Scons file object, e.g. one created with env.File() or as the + result of another build target. + + In the second case, the file is automatically declared as a + dependency of this command. + + Args: + env: The construction Environment() that runs the command. + name: The target file to which the output is to be written. + command: The command to be executed. + + Returns: A command node in the standard SCons format. + """ + deps = [] + for index, arg in enumerate(command): + if not isinstance(arg, str): + if len(Flatten(arg)) != 1: + # Do not allow this, because it would cause "deps" to get out + # of sync with the indexes in "command". + # See http://code.google.com/p/nativeclient/issues/detail?id=1086 + raise AssertionError('Argument to AutoDepsCommand() actually contains ' + 'multiple (or zero) arguments: %r' % arg) + command[index] = '${SOURCES[%d].abspath}' % len(deps) + deps.append(arg) + + return env.Command(name, deps, ' '.join(command)) + +build_env.AddMethod(AutoDepsCommand) + + +def BuildVSSolution(env, target_name, solution, project_config=None): + """BuildVSSolution() Builds a Visual Studio solution. + + Args: + env: The construction Environment() that runs the command. + target_name: The name of the target. Build output will be written to + [target_name]_build_output.txt. + solution: The solution to build. + project_config: A valid project configuration string to pass into devenv. + This provides support for building specific configurations, i.e. + 'Debug|Win32', 'Debug|x64', 'Release|Win32', 'Release|x64'. Note that the + string must be in quotes to work. devenv will default to Win32 if this + is not provided. + + Returns the Command() used to build the target, so other targets can be made + to depend on it. + """ + vs_build_action = ['vcvarsall', '&&', 'devenv', '${SOURCE}', '/build'] + if project_config: + vs_build_action.extend([project_config]) + + build_command = env.Command(target='%s_build_output.txt' % target_name, + source=solution, + action=' '.join(vs_build_action)) + env.AddNodeAliases(build_command, ['bot'], target_name) + return build_command + +build_env.AddMethod(BuildVSSolution) + + +def CleanVSSolution(env, target_name, solution_dir): + """CleanVSSolution() Cleans up a Visual Studio solution's build results. + + The clean target created by this function is added to the 'bot' target as + well as the target specified. The function will clean any build artifacts + that could possibly be generated under the solution directory. + + Args: + env: The construction Environment() that runs the command. + target_name: The name of the target which builds whatever should be cleaned + up. + solution_dir: The directory under which VS build artifacts are to be + expected. This function will look for Debug, Release, and x64 build + targets. + """ + clean_targets = [os.path.join(solution_dir, 'Debug'), + os.path.join(solution_dir, 'Release'), + os.path.join(solution_dir, 'x64')] + + for target in clean_targets: + clean_action = ['rmdir', '/Q', '/S', target] + env.AddCleanAction([target], + Action(' '.join(clean_action)), + ['bot'], + target_name) + +build_env.AddMethod(CleanVSSolution) + + +def TestVSSolution(env, target_name, test_container, type, size, build_cmd): + """Defines a set of tests to be added to the scons test set. + + This function adds a test solution generated by Visual Studio. It can either + run mstest or, for natively compiled solutions, it can run an executable. + + Args: + env: The construction Environment() that runs the command. + target_name: The name of the target which resulted in the test container. + A name that clearly marks the target as a test is recommended here. + test_container: The fully qualified path to the dll or exe that contains + the tests. + type: The type test package to expect as a string. This can be 'dll' or + 'exe'. + size: Which test harness to add the tests to; small, medium, or large + build_cmd: The command which builds the target being tested. + """ + test_action = test_container + if type is 'dll': + test_action = ' '.join(['vcvarsall', + '&&', + 'mstest', + '/testcontainer:%s' % test_container, + '/resultsfile:${TARGET}']) + + # Can't use the test container as SOURCE because it is generated indirectly + # and using it as source would declare an explicit dependency on a target, + # generated by scons. Files that exist in the environment can be used in that + # way, but only if they exist at the time when scons starts to run, or if + # the are explicit Command targets. As a result, source is empty. + test_command = env.Command( + target='%s_test_results.trx' % target_name, + source='', + action=test_action) + + env.Depends(test_command, build_cmd) + env.AddNodeToTestSuite(test_command, + ['bot'], + target_name, + size) + return test_command + +build_env.AddMethod(TestVSSolution) + + +# ---------------------------------------------------------------------------- +BuildComponents(environment_list) + +# Require specifying an explicit target only when not cleaning +if not GetOption('clean'): + Default(None) diff --git a/native_client_sdk/src/project_templates/README b/native_client_sdk/src/project_templates/README new file mode 100644 index 0000000..5aca398 --- /dev/null +++ b/native_client_sdk/src/project_templates/README @@ -0,0 +1,45 @@ +Welcome to the Native Client SDK project_templates directory. + +Currently, this directory contains a mechanism to allow a developer to +bootstrap a native client project and write a lot of the NaCl-specific code +automatically, so the developer can add her own functionality quickly. The +projects created are designed to be self-contained, meaning that they have +everything they need to run, except a server. + +To start a project, run "./init_project.py" or "python init_project.py" +depending on your system configuration. The script will give you usage +information when run with -h or when insufficient or malformed arguments are +provided. + +The result of the script is a project with the name you provide at a location +of your choice. If you have your own server, you may create the project in +a location it serves. Otherwise, you may create a project under the SDK +examples directory and examples/httpd.py to serve your project quickly - at +least temporarily. + +In the future, this directory is intended as a repository for useful stub code, +code snippets, and code generators that can be used to facilitate rapid +development. For now we support initial project setup via init_project.py, but +any generically useful code can be added here if it follows a reasonable +organization. The organization is as follows: + +project_templates: + Contains any top-level scripting elements that apply or may come in useful + for the generation of all NaCl/Pepper2 projects, common Makefile sections, + and this README. +project_templates/[language]: + For any given language there should be a directory with a name that is + commonly associated with that language. +project_templates/[topic] +project_templates/[language]/[topic] + For any given programming topic, such as audio, 2d, or 3d programming, there + should be a directory at the root level for any components that are not + language specific and that may apply to that topic. For corresponding + components that are language specific, a sub-directory for the topic may + also be created under the language directory. + +Note that the layout in this directory does not reflect the layout of the +projects that are created. It is merely a set of simple guidelines to help +organize generic code and utilities so they can be managed here. How +generated projects are laid out is left up to the design of the particular +code-generator. diff --git a/native_client_sdk/src/project_templates/c/build.scons b/native_client_sdk/src/project_templates/c/build.scons new file mode 100644 index 0000000..fcfcf0e --- /dev/null +++ b/native_client_sdk/src/project_templates/c/build.scons @@ -0,0 +1,15 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import os + +nacl_env = make_nacl_env.NaClEnvironment( + nacl_platform=os.getenv('NACL_TARGET_PLATFORM')) + +sources = ['<PROJECT_NAME>.c'] + +nacl_env.AllNaClModules(sources, '<PROJECT_NAME>') diff --git a/native_client_sdk/src/project_templates/c/project_file.c b/native_client_sdk/src/project_templates/c/project_file.c new file mode 100644 index 0000000..7f25bd6 --- /dev/null +++ b/native_client_sdk/src/project_templates/c/project_file.c @@ -0,0 +1,227 @@ +/** @file <PROJECT_NAME>.c + * This example demonstrates loading, running and scripting a very simple + * NaCl module. + */ +#include <stdlib.h> +#include <string.h> +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/pp_module.h" +#include "ppapi/c/pp_var.h" +#include "ppapi/c/ppb.h" +#include "ppapi/c/ppb_instance.h" +#include "ppapi/c/ppb_messaging.h" +#include "ppapi/c/ppb_var.h" +#include "ppapi/c/ppp.h" +#include "ppapi/c/ppp_instance.h" +#include "ppapi/c/ppp_messaging.h" + +static PP_Module module_id = 0; +static struct PPB_Messaging* messaging_interface = NULL; +static struct PPB_Var* var_interface = NULL; + +/** + * Returns a mutable C string contained in the @a var or NULL if @a var is not + * string. This makes a copy of the string in the @a var and adds a NULL + * terminator. Note that VarToUtf8() does not guarantee the NULL terminator on + * the returned string. See the comments for VarToUtf8() in ppapi/c/ppb_var.h + * for more info. The caller is responsible for freeing the returned memory. + * @param[in] var PP_Var containing string. + * @return a mutable C string representation of @a var. + * @note The caller is responsible for freeing the returned string. + */ +/* TODO(sdk_user): 2. Uncomment this when you need it. It is commented out so + * that the compiler doesn't complain about unused functions. + */ +#if 0 +static char* AllocateCStrFromVar(struct PP_Var var) { + uint32_t len = 0; + if (var_interface != NULL) { + const char* var_c_str = var_interface->VarToUtf8(var, &len); + if (len > 0) { + char* c_str = (char*)malloc(len + 1); + memcpy(c_str, var_c_str, len); + c_str[len] = '\0'; + return c_str; + } + } + return NULL; +} +#endif + +/** + * Creates a new string PP_Var from C string. The resulting object will be a + * refcounted string object. It will be AddRef()ed for the caller. When the + * caller is done with it, it should be Release()d. + * @param[in] str C string to be converted to PP_Var + * @return PP_Var containing string. + */ +/* TODO(sdk_user): 3. Uncomment this when you need it. It is commented out so + * that the compiler doesn't complain about unused functions. + */ +#if 0 +static struct PP_Var AllocateVarFromCStr(const char* str) { + if (var_interface != NULL) + return var_interface->VarFromUtf8(module_id, str, strlen(str)); + return PP_MakeUndefined(); +} +#endif + +/** + * Called when the NaCl module is instantiated on the web page. The identifier + * of the new instance will be passed in as the first argument (this value is + * generated by the browser and is an opaque handle). This is called for each + * instantiation of the NaCl module, which is each time the <embed> tag for + * this module is encountered. + * + * If this function reports a failure (by returning @a PP_FALSE), the NaCl + * module will be deleted and DidDestroy will be called. + * @param[in] instance The identifier of the new instance representing this + * NaCl module. + * @param[in] argc The number of arguments contained in @a argn and @a argv. + * @param[in] argn An array of argument names. These argument names are + * supplied in the <embed> tag, for example: + * <embed id="nacl_module" dimensions="2"> + * will produce two arguments, one named "id" and one named "dimensions". + * @param[in] argv An array of argument values. These are the values of the + * arguments listed in the <embed> tag. In the above example, there will + * be two elements in this array, "nacl_module" and "2". The indices of + * these values match the indices of the corresponding names in @a argn. + * @return @a PP_TRUE on success. + */ +static PP_Bool Instance_DidCreate(PP_Instance instance, + uint32_t argc, + const char* argn[], + const char* argv[]) { + return PP_TRUE; +} + +/** + * Called when the NaCl module is destroyed. This will always be called, + * even if DidCreate returned failure. This routine should deallocate any data + * associated with the instance. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + */ +static void Instance_DidDestroy(PP_Instance instance) { +} + +/** + * Called when the position, the size, or the clip rect of the element in the + * browser that corresponds to this NaCl module has changed. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] position The location on the page of this NaCl module. This is + * relative to the top left corner of the viewport, which changes as the + * page is scrolled. + * @param[in] clip The visible region of the NaCl module. This is relative to + * the top left of the plugin's coordinate system (not the page). If the + * plugin is invisible, @a clip will be (0, 0, 0, 0). + */ +static void Instance_DidChangeView(PP_Instance instance, + const struct PP_Rect* position, + const struct PP_Rect* clip) { +} + +/** + * Notification that the given NaCl module has gained or lost focus. + * Having focus means that keyboard events will be sent to the NaCl module + * represented by @a instance. A NaCl module's default condition is that it + * will not have focus. + * + * Note: clicks on NaCl modules will give focus only if you handle the + * click event. You signal if you handled it by returning @a true from + * HandleInputEvent. Otherwise the browser will bubble the event and give + * focus to the element on the page that actually did end up consuming it. + * If you're not getting focus, check to make sure you're returning true from + * the mouse click in HandleInputEvent. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] has_focus Indicates whether this NaCl module gained or lost + * event focus. + */ +static void Instance_DidChangeFocus(PP_Instance instance, + PP_Bool has_focus) { +} + +/** + * Handler that gets called after a full-frame module is instantiated based on + * registered MIME types. This function is not called on NaCl modules. This + * function is essentially a place-holder for the required function pointer in + * the PPP_Instance structure. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] url_loader A PP_Resource an open PPB_URLLoader instance. + * @return PP_FALSE. + */ +static PP_Bool Instance_HandleDocumentLoad(PP_Instance instance, + PP_Resource url_loader) { + /* NaCl modules do not need to handle the document load function. */ + return PP_FALSE; +} + + +/** + * Handler for messages coming in from the browser via postMessage. The + * @a var_message can contain anything: a JSON string; a string that encodes + * method names and arguments; etc. For example, you could use JSON.stringify + * in the browser to create a message that contains a method name and some + * parameters, something like this: + * var json_message = JSON.stringify({ "myMethod" : "3.14159" }); + * nacl_module.postMessage(json_message); + * On receipt of this message in @a var_message, you could parse the JSON to + * retrieve the method name, match it to a function call, and then call it with + * the parameter. + * @param[in] instance The instance ID. + * @param[in] message The contents, copied by value, of the message sent from + * browser via postMessage. + */ +void Messaging_HandleMessage(PP_Instance instance, struct PP_Var var_message) { + /* TODO(sdk_user): 1. Make this function handle the incoming message. */ +} + +/** + * Entry points for the module. + * Initialize instance interface and scriptable object class. + * @param[in] a_module_id Module ID + * @param[in] get_browser_interface Pointer to PPB_GetInterface + * @return PP_OK on success, any other value on failure. + */ +PP_EXPORT int32_t PPP_InitializeModule(PP_Module a_module_id, + PPB_GetInterface get_browser_interface) { + module_id = a_module_id; + var_interface = (struct PPB_Var*)(get_browser_interface(PPB_VAR_INTERFACE)); + messaging_interface = + (struct PPB_Messaging*)(get_browser_interface(PPB_MESSAGING_INTERFACE)); + return PP_OK; +} + +/** + * Returns an interface pointer for the interface of the given name, or NULL + * if the interface is not supported. + * @param[in] interface_name name of the interface + * @return pointer to the interface + */ +PP_EXPORT const void* PPP_GetInterface(const char* interface_name) { + if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0) { + static struct PPP_Instance instance_interface = { + &Instance_DidCreate, + &Instance_DidDestroy, + &Instance_DidChangeView, + &Instance_DidChangeFocus, + &Instance_HandleDocumentLoad + }; + return &instance_interface; + } else if (strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0) { + static struct PPP_Messaging messaging_interface = { + &Messaging_HandleMessage + }; + return &messaging_interface; + } + return NULL; +} + +/** + * Called before the plugin module is unloaded. + */ +PP_EXPORT void PPP_ShutdownModule() { +} diff --git a/native_client_sdk/src/project_templates/cc/build.scons b/native_client_sdk/src/project_templates/cc/build.scons new file mode 100644 index 0000000..537d17b --- /dev/null +++ b/native_client_sdk/src/project_templates/cc/build.scons @@ -0,0 +1,16 @@ +#! -*- python -*- +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import make_nacl_env +import nacl_utils +import os + +nacl_env = make_nacl_env.NaClEnvironment( + use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM')) + +sources = ['<PROJECT_NAME>.cc'] + +nacl_env.AllNaClModules(sources, '<PROJECT_NAME>') diff --git a/native_client_sdk/src/project_templates/cc/project_file.cc b/native_client_sdk/src/project_templates/cc/project_file.cc new file mode 100644 index 0000000..c0123fe --- /dev/null +++ b/native_client_sdk/src/project_templates/cc/project_file.cc @@ -0,0 +1,87 @@ +/// @file <PROJECT_NAME>.cc +/// This example demonstrates loading, running and scripting a very simple NaCl +/// module. To load the NaCl module, the browser first looks for the +/// CreateModule() factory method (at the end of this file). It calls +/// CreateModule() once to load the module code from your .nexe. After the +/// .nexe code is loaded, CreateModule() is not called again. +/// +/// Once the .nexe code is loaded, the browser than calls the CreateInstance() +/// method on the object returned by CreateModule(). It calls CreateInstance() +/// each time it encounters an <embed> tag that references your NaCl module. +/// +/// The browser can talk to your NaCl module via the postMessage() Javascript +/// function. When you call postMessage() on your NaCl module from the browser, +/// this becomes a call to the HandleMessage() method of your pp::Instance +/// subclass. You can send messages back to the browser by calling the +/// PostMessage() method on your pp::Instance. Note that these two methods +/// (postMessage() in Javascript and PostMessage() in C++) are asynchronous. +/// This means they return immediately - there is no waiting for the message +/// to be handled. This has implications in your program design, particularly +/// when mutating property values that are exposed to both the browser and the +/// NaCl module. + +#include <cstdio> +#include <string> +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/var.h" + +/// The Instance class. One of these exists for each instance of your NaCl +/// module on the web page. The browser will ask the Module object to create +/// a new Instance for each occurence of the <embed> tag that has these +/// attributes: +/// type="application/x-nacl" +/// src="<PROJECT_NAME>.nmf" +/// To communicate with the browser, you must override HandleMessage() for +/// receiving messages from the borwser, and use PostMessage() to send messages +/// back to the browser. Note that this interface is entirely asynchronous. +class <ProjectName>Instance : public pp::Instance { + public: + /// The constructor creates the plugin-side instance. + /// @param[in] instance the handle to the browser-side plugin instance. + explicit <ProjectName>Instance(PP_Instance instance) : pp::Instance(instance) + {} + virtual ~<ProjectName>Instance() {} + + /// Handler for messages coming in from the browser via postMessage(). The + /// @a var_message can contain anything: a JSON string; a string that encodes + /// method names and arguments; etc. For example, you could use + /// JSON.stringify in the browser to create a message that contains a method + /// name and some parameters, something like this: + /// var json_message = JSON.stringify({ "myMethod" : "3.14159" }); + /// nacl_module.postMessage(json_message); + /// On receipt of this message in @a var_message, you could parse the JSON to + /// retrieve the method name, match it to a function call, and then call it + /// with the parameter. + /// @param[in] var_message The message posted by the browser. + virtual void HandleMessage(const pp::Var& var_message) { + // TODO(sdk_user): 1. Make this function handle the incoming message. + } +}; + +/// The Module class. The browser calls the CreateInstance() method to create +/// an instance of your NaCl module on the web page. The browser creates a new +/// instance for each <embed> tag with type="application/x-nacl". +class <ProjectName>Module : public pp::Module { + public: + <ProjectName>Module() : pp::Module() {} + virtual ~<ProjectName>Module() {} + + /// Create and return a <ProjectName>Instance object. + /// @param[in] instance The browser-side instance. + /// @return the plugin-side instance. + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new <ProjectName>Instance(instance); + } +}; + +namespace pp { +/// Factory function called by the browser when the module is first loaded. +/// The browser keeps a singleton of this module. It calls the +/// CreateInstance() method on the object you return to make instances. There +/// is one instance per <embed> tag on the page. This is the main binding +/// point for your NaCl module with the browser. +Module* CreateModule() { + return new <ProjectName>Module(); +} +} // namespace pp diff --git a/native_client_sdk/src/project_templates/html/project_file.html b/native_client_sdk/src/project_templates/html/project_file.html new file mode 100644 index 0000000..0b21ad9 --- /dev/null +++ b/native_client_sdk/src/project_templates/html/project_file.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<html> +<head> + <title><ProjectName>!</title> + + <script type="text/javascript"> + <ProjectName>Module = null; // Global application object. + statusText = 'NO-STATUS'; + + // Indicate load success. + function moduleDidLoad() { + <ProjectName>Module = document.getElementById('<PROJECT_NAME>'); + updateStatus('SUCCESS'); + } + + // The 'message' event handler. This handler is fired when the NaCl module + // posts a message to the browser by calling PPB_Messaging.PostMessage() + // (in C) or pp::Instance.PostMessage() (in C++). This implementation + // simply displays the content of the message in an alert panel. + function handleMessage(message_event) { + alert(message_event.data); + } + + // If the page loads before the Native Client module loads, then set the + // status message indicating that the module is still loading. Otherwise, + // do not change the status message. + function pageDidLoad() { + if (<ProjectName>Module == null) { + updateStatus('LOADING...'); + } else { + // It's possible that the Native Client module onload event fired + // before the page's onload event. In this case, the status message + // will reflect 'SUCCESS', but won't be displayed. This call will + // display the current message. + updateStatus(); + } + } + + // Set the global status message. If the element with id 'statusField' + // exists, then set its HTML to the status message as well. + // opt_message The message test. If this is null or undefined, then + // attempt to set the element with id 'statusField' to the value of + // |statusText|. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + var statusField = document.getElementById('status_field'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + </script> +</head> +<body onload="pageDidLoad()"> + +<h1>Native Client Module <ProjectName></h1> +<p> + <!-- Load the published .nexe. This includes the 'nacl' attribute which + shows how to load multi-architecture modules. Each entry in the "nexes" + object in the .nmf manifest file is a key-value pair: the key is the + instruction set architecture ('x86-32', 'x86-64', etc.); the value is a URL + for the desired NaCl module. + To load the debug versions of your .nexes, set the 'nacl' attribute to the + _dbg.nmf version of the manifest file. + + Note: Since this NaCl module does not use any real-estate in the browser, + it's width and height are set to 0. + + Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load' + and a 'message' event listener attached. This wrapping method is used + instead of attaching the event listeners directly to the <EMBED> element to + ensure that the listeners are active before the NaCl module 'load' event + fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or + pp::Instance.PostMessage() (in C++) from within the initialization code in + your NaCl module. + --> + <div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener'); + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + + <embed name="nacl_module" + id="<PROJECT_NAME>" + width=0 height=0 + src="<PROJECT_NAME>.nmf" + type="application/x-nacl" /> + </div> +</p> + +<h2>Status</h2> +<div id="status_field">NO-STATUS</div> +</body> +</html> diff --git a/native_client_sdk/src/project_templates/init_project.py b/native_client_sdk/src/project_templates/init_project.py new file mode 100755 index 0000000..4b5bad2 --- /dev/null +++ b/native_client_sdk/src/project_templates/init_project.py @@ -0,0 +1,502 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""A simple project generator for Native Client projects written in C or C++. + +This script accepts a few argument which it uses as a description of a new NaCl +project. It sets up a project with a given name and a given primary language +(default: C++, optionally, C) using the appropriate files from this area. +This script does not handle setup for complex applications, just the basic +necessities to get a functional native client application stub. When this +script terminates a compileable project stub will exist with the specified +name, at the specified location. + +GetCamelCaseName(): Converts an underscore name to a camel case name. +GetCodeDirectory(): Decides what directory to pull source code from. +GetCodeSoureFiles(): Decides what source files to pull into the stub. +GetCommonSourceFiles(): Gives list of files needed by all project types. +GetHTMLDirectory(): Decides what directory to pull HTML stub from. +GetHTMLSourceFiles(): Gives HTML files to be included in project stub. +GetTargetFileName(): Converts a source file name into a project file name. +ParseArguments(): Parses the arguments provided by the user. +ReplaceInFile(): Replaces a given string with another in a given file. +ProjectInitializer: Maintains some state applicable to setting up a project. +main(): Executes the script. +""" + +__author__ = 'mlinck@google.com (Michael Linck)' + +import fileinput +import optparse +import os.path +import shutil +import sys +import uuid + +# A list of all platforms that should have make.cmd. +WINDOWS_BUILD_PLATFORMS = ['cygwin', 'win32'] + +# Tags that will be replaced in our the new project's source files. +PROJECT_NAME_TAG = '<PROJECT_NAME>' +PROJECT_NAME_CAMEL_CASE_TAG = '<ProjectName>' +SDK_ROOT_TAG = '<NACL_SDK_ROOT>' +NACL_PLATFORM_TAG = '<NACL_PLATFORM>' +VS_PROJECT_UUID_TAG = '<VS_PROJECT_UUID>' +VS_SOURCE_UUID_TAG = '<VS_SOURCE_UUID>' +VS_HEADER_UUID_TAG = '<VS_HEADER_UUID>' +VS_RESOURCE_UUID_TAG = '<VS_RESOURCE_UUID>' + +# This string is the part of the file name that will be replaced. +PROJECT_FILE_NAME = 'project_file' + +# Lists of source files that will be used for the new project. +COMMON_PROJECT_FILES = ['scons'] +C_SOURCE_FILES = ['build.scons', '%s.c' % PROJECT_FILE_NAME] +CC_SOURCE_FILES = ['build.scons', '%s.cc' % PROJECT_FILE_NAME] +HTML_FILES = ['%s.html' % PROJECT_FILE_NAME] +VS_FILES = ['%s.sln' % PROJECT_FILE_NAME, '%s.vcproj' % PROJECT_FILE_NAME] + +# Error needs to be a class, since we 'raise' it in several places. +class Error(Exception): + pass + + +def GetCamelCaseName(lower_case_name): + """Converts an underscore name to a camel case name. + + Args: + lower_case_name: The name in underscore-delimited lower case format. + + Returns: + The name in camel case format. + """ + camel_case_name = '' + name_parts = lower_case_name.split('_') + for part in name_parts: + if part: + camel_case_name += part.capitalize() + return camel_case_name + + +def GetCodeDirectory(is_c_project, project_templates_dir): + """Decides what directory to pull source code from. + + Args: + is_c_project: A boolean indicating whether this project is in C or not. + project_templates_dir: The path to the project_templates directory. + + Returns: + The code directory for the given project type. + """ + stub_directory = '' + if is_c_project: + stub_directory = os.path.join(project_templates_dir, 'c') + else: + stub_directory = os.path.join(project_templates_dir, 'cc') + return stub_directory + + +def GetCodeSourceFiles(is_c_project): + """Decides what source files to pull into the stub. + + Args: + is_c_project: A boolean indicating whether this project is in C or not. + + Returns: + The files that are specific to the requested type of project and live in its + directory. + """ + project_files = [] + if is_c_project: + project_files = C_SOURCE_FILES + else: + project_files = CC_SOURCE_FILES + return project_files + + +def GetCommonSourceFiles(): + """Gives list of files needed by all project types. + + Returns: + The files C and C++ projects have in common. These are the files that live + in the top level project_templates directory. + """ + project_files = COMMON_PROJECT_FILES + if sys.platform in WINDOWS_BUILD_PLATFORMS: + project_files.extend(['scons.bat']) + return project_files + + +def GetVsDirectory(project_templates_dir): + """Decides what directory to pull Visual Studio stub from. + + Args: + project_templates_dir: The path to the project_templates directory. + + Returns: + The directory where the HTML stub is to be found. + """ + return os.path.join(project_templates_dir, 'vs') + + +def GetVsProjectFiles(): + """Gives VisualStudio files to be included in project stub. + + Returns: + The VisualStudio files needed for the project. + """ + return VS_FILES + + +def GetHTMLDirectory(project_templates_dir): + """Decides what directory to pull HTML stub from. + + Args: + project_templates_dir: The path to the project_templates directory. + + Returns: + The directory where the HTML stub is to be found. + """ + return os.path.join(project_templates_dir, 'html') + + +def GetHTMLSourceFiles(): + """Gives HTML files to be included in project stub. + + Returns: + The HTML files needed for the project. + """ + return HTML_FILES + + +def GetTargetFileName(source_file_name, project_name): + """Converts a source file name into a project file name. + + Args: + source_file_name: The name of a file that is to be included in the project + stub, as it appears at the source location. + project_name: The name of the project that is being generated. + + Returns: + The target file name for a given source file. All project files are run + through this filter and it modifies them as needed. + """ + target_file_name = '' + if source_file_name.startswith(PROJECT_FILE_NAME): + target_file_name = source_file_name.replace(PROJECT_FILE_NAME, + project_name) + else: + target_file_name = source_file_name + return target_file_name + + +def GetDefaultProjectDir(): + """Determines the default project directory. + + The default directory root for new projects is called 'nacl_projects' under + the user's home directory. There are two ways to override this: you can set + the NACL_PROJECT_ROOT environment variable, or use the --directory option. + + Returns: + An os-specific path to the default project directory, which is called + 'nacl_projects' under the user's home directory. + """ + return os.getenv('NACL_PROJECT_ROOT', + os.path.join(os.path.expanduser('~'), 'nacl_projects')) + + +def ParseArguments(argv): + """Parses the arguments provided by the user. + + Parses the command line options and makes sure the script errors when it is + supposed to. + + Args: + argv: The argument array. + + Returns: + The options structure that represents the arguments after they have been + parsed. + """ + parser = optparse.OptionParser() + parser.add_option( + '-n', '--name', dest='project_name', + default='', + help=('Required: the name of the new project to be stubbed out.\n' + 'Please use lower case names with underscore, i.e. hello_world.')) + parser.add_option( + '-d', '--directory', dest='project_directory', + default=GetDefaultProjectDir(), + help=('Optional: If set, the new project will be created under this ' + 'directory and the directory created if necessary.')) + parser.add_option( + '-c', action='store_true', dest='is_c_project', + default=False, + help=('Optional: If set, this will generate a C project. Default ' + 'is C++.')) + parser.add_option( + '-p', '--nacl-platform', dest='nacl_platform', + default='pepper_17', + help=('Optional: if set, the new project will target the given nacl\n' + 'platform. Default is the most current platform. e.g. pepper_17')) + parser.add_option( + '--vsproj', action='store_true', dest='is_vs_project', + default=False, + help=('Optional: If set, generate Visual Studio project files.')) + result = parser.parse_args(argv) + options = result[0] + args = result[1] + #options, args) = parser.parse_args(argv) + if args: + parser.print_help() + sys.exit(1) + elif not options.project_name.islower(): + print('--name missing or in incorrect format. Please use -h for ' + 'instructions.') + sys.exit(1) + return options + + +class ProjectInitializer(object): + """Maintains the state of the project that is being created.""" + + def __init__(self, is_c_project, is_vs_project, project_name, + project_location, nacl_platform, project_templates_dir, + os_resource=os): + """Initializes all the fields that are known after parsing the parameters. + + Args: + is_c_project: A boolean indicating whether this project is in C or not. + is_vs_project: A boolean indicating whether this project has Visual + Studio support. + project_name: A string containing the name of the project to be created. + project_location: A path indicating where the new project is to be placed. + project_templates_dir: The path to the project_templates directory. + os_resource: A resource to be used as os. Provided for unit testing. + """ + self.__is_c_project = is_c_project + self.__is_vs_project = is_vs_project + self.__project_files = [] + self.__project_name = project_name + self.__project_location = project_location + self.__nacl_platform = nacl_platform + self.__project_templates_dir = project_templates_dir + # System resources are properties so mocks can be inserted. + self.__fileinput = fileinput + self.__os = os_resource + self.__shutil = shutil + self.__sys = sys + self.__CreateProjectDirectory() + + def CopyAndRenameFiles(self, source_dir, file_names): + """Places files in the new project's directory and renames them as needed. + + Copies the given files from the given source directory into the new + project's directory, renaming them as necessary. Each file that is created + in the project directory is also added to self.__project_files. + + Args: + source_dir: A path indicating where the files are to be copied from. + file_names: The list of files that is to be copied out of source_dir. + """ + for source_file_name in file_names: + target_file_name = GetTargetFileName(source_file_name, + self.__project_name) + copy_source_file = self.os.path.join(source_dir, source_file_name) + copy_target_file = self.os.path.join(self.__project_dir, target_file_name) + self.shutil.copy(copy_source_file, copy_target_file) + self.__project_files += [copy_target_file] + + def __CreateProjectDirectory(self): + """Creates the project's directory and any parents as necessary.""" + self.__project_dir = self.os.path.join(self.__project_location, + self.__project_name) + if self.os.path.exists(self.__project_dir): + raise Error("Error: directory '%s' already exists" % self.__project_dir) + self.os.makedirs(self.__project_dir) + + def PrepareDirectoryContent(self): + """Prepares the directory for the new project. + + This function's job is to know what directories need to be used and what + files need to be copied and renamed. It uses several tiny helper functions + to do this. + There are three locations from which files are copied to create a project. + That number may change in the future. + """ + code_source_dir = GetCodeDirectory(self.__is_c_project, + self.__project_templates_dir) + code_source_files = GetCodeSourceFiles(self.__is_c_project) + html_source_dir = GetHTMLDirectory(self.__project_templates_dir) + html_source_files = GetHTMLSourceFiles() + common_source_files = GetCommonSourceFiles() + self.CopyAndRenameFiles(code_source_dir, code_source_files) + self.CopyAndRenameFiles(html_source_dir, html_source_files) + self.CopyAndRenameFiles(self.__project_templates_dir, + common_source_files) + if self.__is_vs_project: + vs_source_dir = GetVsDirectory(self.__project_templates_dir) + vs_files = GetVsProjectFiles() + self.CopyAndRenameFiles(vs_source_dir, vs_files) + print('init_project has copied the appropriate files to: %s' % + self.__project_dir) + + def PrepareFileContent(self): + """Changes contents of files in the new project as needed. + + Goes through each file in the project that is being created and replaces + contents as necessary. + """ + camel_case_name = GetCamelCaseName(self.__project_name) + sdk_root_dir = os.getenv('NACL_SDK_ROOT', None) + if not sdk_root_dir: + raise Error("Error: NACL_SDK_ROOT is not set") + sdk_root_dir = self.os.path.abspath(sdk_root_dir) + if self.__is_vs_project: + project_uuid = str(uuid.uuid4()).upper() + vs_source_uuid = str(uuid.uuid4()).upper() + vs_header_uuid = str(uuid.uuid4()).upper() + vs_resource_uuid = str(uuid.uuid4()).upper() + for project_file in self.__project_files: + self.ReplaceInFile(project_file, PROJECT_NAME_TAG, self.__project_name) + self.ReplaceInFile(project_file, + PROJECT_NAME_CAMEL_CASE_TAG, + camel_case_name) + self.ReplaceInFile(project_file, SDK_ROOT_TAG, sdk_root_dir) + self.ReplaceInFile(project_file, NACL_PLATFORM_TAG, self.__nacl_platform) + if self.__is_vs_project: + self.ReplaceInFile(project_file, VS_PROJECT_UUID_TAG, project_uuid) + self.ReplaceInFile(project_file, VS_SOURCE_UUID_TAG, vs_source_uuid) + self.ReplaceInFile(project_file, VS_HEADER_UUID_TAG, vs_header_uuid) + self.ReplaceInFile(project_file, VS_RESOURCE_UUID_TAG, vs_resource_uuid) + + def ReplaceInFile(self, file_path, old_text, new_text): + """Replaces a given string with another in a given file. + + Args: + file_path: The path to the file that is to be modified. + old_text: The text that is to be removed. + new_text: The text that is to be added in place of old_text. + """ + for line in self.fileinput.input(file_path, inplace=1, mode='U'): + self.sys.stdout.write(line.replace(old_text, new_text)) + + # The following properties exist to make unit testing possible. + + def _GetFileinput(self): + """Accessor for Fileinput property.""" + return self.__fileinput + + def __GetFileinput(self): + """Indirect Accessor for _GetFileinput.""" + return self._GetFileinput() + + def _SetFileinput(self, fileinput_resource): + """Accessor for Fileinput property.""" + self.__fileinput = fileinput_resource + + def __SetFileinput(self, fileinput_resource): + """Indirect Accessor for _SetFileinput.""" + return self._SetFileinput(fileinput_resource) + + fileinput = property( + __GetFileinput, __SetFileinput, + doc="""Gets and sets the resource to use as fileinput.""") + + def _GetOS(self): + """Accessor for os property.""" + return self.__os + + def __GetOS(self): + """Indirect Accessor for _GetOS.""" + return self._GetOS() + + def _SetOS(self, os_resource): + """Accessor for os property.""" + self.__os = os_resource + + def __SetOS(self, os_resource): + """Indirect Accessor for _SetOS.""" + return self._SetOS(os_resource) + + os = property(__GetOS, __SetOS, + doc="""Gets and sets the resource to use as os.""") + + def _GetShutil(self): + """Accessor for shutil property.""" + return self.__shutil + + def __GetShutil(self): + """Indirect Accessor for _GetShutil.""" + return self._GetShutil() + + def _SetShutil(self, shutil_resource): + """Accessor for shutil property.""" + self.__shutil = shutil_resource + + def __SetShutil(self, shutil_resource): + """Indirect Accessor for _SetShutil.""" + return self._SetShutil(shutil_resource) + + shutil = property(__GetShutil, __SetShutil, + doc="""Gets and sets the resource to use as shutil.""") + + def _GetSys(self): + """Accessor for sys property.""" + return self.__sys + + def __GetSys(self): + """Indirect Accessor for _GetSys.""" + return self._GetSys() + + def _SetSys(self, sys_resource): + """Accessor for sys property.""" + self.__sys = sys_resource + + def __SetSys(self, sys_resource): + """Indirect Accessor for _SetSys.""" + return self._SetSys(sys_resource) + + sys = property(__GetSys, __SetSys, + doc="""Gets and sets the resource to use as sys.""") + + +def main(argv): + """Prepares the new project. + + Args: + argv: The arguments passed to the script by the shell. + """ + print 'init_project parsing its arguments.' + script_dir = os.path.abspath(os.path.dirname(__file__)) + options = ParseArguments(argv) + print 'init_project is preparing your project.' + # Check to see if the project is going into the SDK bundle. If so, issue a + # warning. + sdk_root_dir = os.getenv('NACL_SDK_ROOT', None) + if sdk_root_dir: + if os.path.normpath(options.project_directory).count( + os.path.normpath(sdk_root_dir)) > 0: + print('WARNING: It looks like you are creating projects in the NaCl SDK ' + 'directory %s.\nThese might be removed at the next update.' % + sdk_root_dir) + project_initializer = ProjectInitializer(options.is_c_project, + options.is_vs_project, + options.project_name, + options.project_directory, + options.nacl_platform, + script_dir) + project_initializer.PrepareDirectoryContent() + project_initializer.PrepareFileContent() + return 0 + + +if __name__ == '__main__': + try: + sys.exit(main(sys.argv[1:])) + except Exception as error: + print error + sys.exit(1) diff --git a/native_client_sdk/src/project_templates/init_project_test.py b/native_client_sdk/src/project_templates/init_project_test.py new file mode 100755 index 0000000..681916f --- /dev/null +++ b/native_client_sdk/src/project_templates/init_project_test.py @@ -0,0 +1,256 @@ +#!/usr/bin/python2.6 +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for init_project.py.""" + +__author__ = 'mlinck@google.com (Michael Linck)' + +import fileinput +import os +import shutil +import sys +import unittest +import mox +import init_project + + +def TestMock(file_path, open_func): + temp_file = open_func(file_path) + temp_file.close() + + +class TestGlobalFunctions(unittest.TestCase): + """Class for test cases to cover globally declared helper functions.""" + + def testGetCamelCaseName(self): + output = init_project.GetCamelCaseName('camel_case_name') + self.assertEqual(output, 'CamelCaseName') + output = init_project.GetCamelCaseName('print_42') + self.assertEqual(output, 'Print42') + + def testGetCodeDirectory(self): + output = init_project.GetCodeDirectory(True, '') + self.assertEqual(output, 'c') + output = init_project.GetCodeDirectory(False, '') + self.assertEqual(output, 'cc') + output = init_project.GetCodeDirectory(True, 'test') + self.assertEqual(output, 'test/c') + output = init_project.GetCodeDirectory(False, 'test') + self.assertEqual(output, 'test/cc') + + def testGetCodeSourceFiles(self): + output = init_project.GetCodeSourceFiles(False) + self.assertEqual(output, init_project.CC_SOURCE_FILES) + output = init_project.GetCodeSourceFiles(True) + self.assertEqual(output, init_project.C_SOURCE_FILES) + + def testGetCommonSourceFiles(self): + output = init_project.GetCommonSourceFiles() + expected_output_linux = init_project.COMMON_PROJECT_FILES + expected_output_windows = init_project.COMMON_PROJECT_FILES + expected_output_windows.extend(['scons.bat']) + linux_match = (output == expected_output_linux) + windows_match = (output == expected_output_windows) + passed = (linux_match | windows_match) + self.assertTrue(passed) + + def testGetHTMLDirectory(self): + output = init_project.GetHTMLDirectory('') + self.assertEqual(output, 'html') + output = init_project.GetHTMLDirectory('test') + self.assertEqual(output, 'test/html') + + def testGetHTMLSourceFiles(self): + output = init_project.GetHTMLSourceFiles() + self.assertEqual(output, init_project.HTML_FILES) + + def testGetTargetFileName(self): + output = init_project.GetTargetFileName('project_file.cc', 'bonkers') + self.assertEqual(output, 'bonkers.cc') + output = init_project.GetTargetFileName('constant.html', 'bonkers') + self.assertEqual(output, 'constant.html') + + def testParseArguments(self): + output = init_project.ParseArguments(['-n', 'test_name', '-d', 'test/dir']) + self.assertEqual(output.is_c_project, False) + self.assertEqual(output.project_name, 'test_name') + self.assertEqual(output.project_directory, 'test/dir') + output = init_project.ParseArguments(['-n', 'test_name_2', '-c']) + self.assertEqual(output.is_c_project, True) + self.assertEqual(output.project_name, 'test_name_2') + self.assertEqual(output.project_directory, + init_project.GetDefaultProjectDir()) + + +class TestProjectInitializer(unittest.TestCase): + """Class for test cases to cover public interface of ProjectInitializer.""" + + def setUp(self): + self.script_dir = os.path.abspath(os.path.dirname(__file__)) + self.nacl_src_dir = os.getenv('NACL_SDK_ROOT', None) + self.mock_factory = mox.Mox() + # This mock is only valid for initialization and will be overwritten + # after ward by self.os_mock. + init_path_mock = self.mock_factory.CreateMock(os.path) + init_path_mock.join('test/dir', 'test_project').AndReturn( + 'test/dir/test_project') + init_path_mock.exists('test/dir/test_project').AndReturn(False) + init_os_mock = self.mock_factory.CreateMock(os) + init_os_mock.path = init_path_mock + init_os_mock.makedirs('test/dir/test_project') + self.mock_factory.ReplayAll() + self.test_subject = init_project.ProjectInitializer( + # True => is C project, False => is vs project + True, False, 'test_project', 'test/dir', 'pepper_14', self.script_dir, + init_os_mock) + self.mock_factory.VerifyAll() + self.InitializeResourceMocks() + + def InitializeResourceMocks(self): + """Can be called multiple times if multiple functions need to be tested.""" + self.fileinput_mock = self.mock_factory.CreateMock(fileinput) + self.test_subject.fileinput = self.fileinput_mock + self.os_mock = self.mock_factory.CreateMock(os) + self.test_subject.os = self.os_mock + self.shutil_mock = self.mock_factory.CreateMock(shutil) + self.test_subject.shutil = self.shutil_mock + self.sys_mock = self.mock_factory.CreateMock(sys) + self.test_subject.sys = self.sys_mock + + def testCopyAndRenameFiles(self): + self.shutil_mock.copy('source/dir/normal_name.txt', + 'test/dir/test_project/normal_name.txt') + self.shutil_mock.copy('source/dir/project_file.txt', + 'test/dir/test_project/test_project.txt') + self.os_mock.path = os.path + self.mock_factory.ReplayAll() + self.test_subject.CopyAndRenameFiles( + 'source/dir', ['normal_name.txt', 'project_file.txt']) + self.mock_factory.VerifyAll() + + def testPrepareDirectoryContent(self): + self.shutil_mock.copy( + '%s/c/build.scons' % self.script_dir, + 'test/dir/test_project/build.scons') + self.shutil_mock.copy( + '%s/c/project_file.c' % self.script_dir, + 'test/dir/test_project/test_project.c') + self.shutil_mock.copy( + '%s/html/project_file.html' % self.script_dir, + 'test/dir/test_project/test_project.html') + self.shutil_mock.copy( + '%s/scons' % self.script_dir, + 'test/dir/test_project/scons') + self.shutil_mock.copy( + '%s/scons.bat' % self.script_dir, + 'test/dir/test_project/scons.bat') + self.os_mock.path = os.path + self.mock_factory.ReplayAll() + self.test_subject.PrepareDirectoryContent() + self.mock_factory.VerifyAll() + + def testPrepareFileContent(self): + self.testCopyAndRenameFiles() + # We need a new set of resource mocks since the old ones have already been + # used. + self.InitializeResourceMocks() + path_mock = self.mock_factory.CreateMock(os.path) + stdout_mock = self.mock_factory.CreateMock(sys.stdout) + self.os_mock.path = path_mock + path_mock.abspath(self.nacl_src_dir).AndReturn(self.nacl_src_dir) + self.fileinput_mock.input( + 'test/dir/test_project/normal_name.txt', + inplace=1, mode='U').AndReturn( + ['A line with <PROJECT_NAME>.', + 'A line with <ProjectName>.', + 'A line with <NACL_SDK_ROOT>.', + 'A line with <NACL_PLATFORM>.']) + stdout_mock.write('A line with test_project.') + stdout_mock.write('A line with <ProjectName>.') + stdout_mock.write('A line with <NACL_SDK_ROOT>.') + stdout_mock.write('A line with <NACL_PLATFORM>.') + self.fileinput_mock.input( + 'test/dir/test_project/normal_name.txt', + inplace=1, mode='U').AndReturn( + ['A line with test_project.', + 'A line with <ProjectName>.', + 'A line with <NACL_SDK_ROOT>.', + 'A line with <NACL_PLATFORM>.']) + stdout_mock.write('A line with test_project.') + stdout_mock.write('A line with TestProject.') + stdout_mock.write('A line with <NACL_SDK_ROOT>.') + stdout_mock.write('A line with <NACL_PLATFORM>.') + self.fileinput_mock.input( + 'test/dir/test_project/normal_name.txt', + inplace=1, mode='U').AndReturn( + ['A line with test_project.', + 'A line with TestProject.', + 'A line with <NACL_SDK_ROOT>.', + 'A line with <NACL_PLATFORM>.']) + stdout_mock.write('A line with test_project.') + stdout_mock.write('A line with TestProject.') + stdout_mock.write('A line with %s.' % self.nacl_src_dir) + stdout_mock.write('A line with <NACL_PLATFORM>.') + self.fileinput_mock.input( + 'test/dir/test_project/normal_name.txt', + inplace=1, mode='U').AndReturn( + ['A line with test_project.', + 'A line with TestProject.', + 'A line with some/dir.', + 'A line with <NACL_PLATFORM>.']) + stdout_mock.write('A line with test_project.') + stdout_mock.write('A line with TestProject.') + stdout_mock.write('A line with some/dir.') + stdout_mock.write('A line with pepper_14.') + # One multi-line file with different replacements has already been mocked + # so we make this next test simpler. + self.fileinput_mock.input( + 'test/dir/test_project/test_project.txt', + inplace=1, mode='U').AndReturn(['A line with no replaceable text.']) + stdout_mock.write('A line with no replaceable text.') + self.fileinput_mock.input( + 'test/dir/test_project/test_project.txt', + inplace=1, mode='U').AndReturn(['A line with no replaceable text.']) + stdout_mock.write('A line with no replaceable text.') + self.fileinput_mock.input( + 'test/dir/test_project/test_project.txt', + inplace=1, mode='U').AndReturn(['A line with no replaceable text.']) + stdout_mock.write('A line with no replaceable text.') + self.fileinput_mock.input( + 'test/dir/test_project/test_project.txt', + inplace=1, mode='U').AndReturn(['A line with no replaceable text.']) + stdout_mock.write('A line with no replaceable text.') + self.sys_mock.stdout = stdout_mock + self.mock_factory.ReplayAll() + self.test_subject.PrepareFileContent() + self.mock_factory.VerifyAll() + + def testReplaceInFile(self): + self.fileinput_mock.input('test/path', inplace=1, mode='U').AndReturn( + ['A sentence replace_me.']) + stdout_mock = self.mock_factory.CreateMock(sys.stdout) + stdout_mock.write('A sentence with_this.') + self.sys_mock.stdout = stdout_mock + self.mock_factory.ReplayAll() + self.test_subject.ReplaceInFile('test/path', 'replace_me', 'with_this') + self.mock_factory.VerifyAll() + + +def RunTests(): + # It's possible to do this with one suite instead of two, but then it's + # harder to read the test output. + return_value = 1 + suite_one = unittest.TestLoader().loadTestsFromTestCase(TestGlobalFunctions) + result_one = unittest.TextTestRunner(verbosity=2).run(suite_one) + suite_two = unittest.TestLoader().loadTestsFromTestCase( + TestProjectInitializer) + result_two = unittest.TextTestRunner(verbosity=2).run(suite_two) + if result_one.wasSuccessful() and result_two.wasSuccessful(): + return_value = 0 + return return_value + +if __name__ == '__main__': + sys.exit(RunTests()) diff --git a/native_client_sdk/src/project_templates/scons b/native_client_sdk/src/project_templates/scons new file mode 100755 index 0000000..2b7e0d3 --- /dev/null +++ b/native_client_sdk/src/project_templates/scons @@ -0,0 +1,32 @@ +#!/bin/bash +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +readonly SCRIPT_DIR="$(dirname "$0")" +readonly SCRIPT_DIR_ABS="$(cd "${SCRIPT_DIR}" ; pwd -P)" + +export NACL_SDK_ROOT=<NACL_SDK_ROOT> +# NACL_TARGET_PLATFORM is really the name of a folder with the base dir - +# usually NACL_SDK_ROOT - within which the toolchain for the target platform +# are found. +# Replace the platform with the name of your target platform. For example, to +# build applications that target the pepper_17 API, set +# NACL_TARGET_PLATFORM="pepper_17" +export NACL_TARGET_PLATFORM="<NACL_PLATFORM>" + +readonly NACL_PLATFORM_DIR="${NACL_SDK_ROOT}/${NACL_TARGET_PLATFORM}" +readonly BASE_SCRIPT="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/script/scons" + +export SCONS_LIB_DIR="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/engine" +export PYTHONPATH="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/engine" +# We have to do this because scons overrides PYTHONPATH and does not preserve +# what is provided by the OS. The custom variable name won't be overwritten. +export PYMOX="${NACL_PLATFORM_DIR}/third_party/pymox" + +"${BASE_SCRIPT}" --file=build.scons \ + --site-dir="${NACL_PLATFORM_DIR}/build_tools/nacl_sdk_scons" \ + $* + diff --git a/native_client_sdk/src/project_templates/scons.bat b/native_client_sdk/src/project_templates/scons.bat new file mode 100755 index 0000000..f487e28 --- /dev/null +++ b/native_client_sdk/src/project_templates/scons.bat @@ -0,0 +1,31 @@ +@echo off + +:: Copyright (c) 2011 The Native Client Authors. All rights reserved. +:: Use of this source code is governed by a BSD-style license that can be +:: found in the LICENSE file. + +setlocal + +set NACL_SDK_ROOT=<NACL_SDK_ROOT> +:: NACL_TARGET_PLATFORM is really the name of a folder with the base dir - +:: usually NACL_SDK_ROOT - within which the toolchain for the target platform +:: are found. +:: Replace the platform with the name of your target platform. For example, to +:: build applications that target the pepper_17 API, set +:: NACL_TARGET_PLATFORM=pepper_17 +set NACL_TARGET_PLATFORM=<NACL_PLATFORM> + +set NACL_PLATFORM_DIR=%NACL_SDK_ROOT%\%NACL_TARGET_PLATFORM% + +:: Set the PYTHONPATH and SCONS_LIB_DIR so we can import SCons modules +set SCONS_LIB_DIR=%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\engine +set PYTHONPATH=%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\engine + +:: We have to do this because scons overrides PYTHONPATH and does not preserve +:: what is provided by the OS. The custom variable name won't be overwritten. +set PYMOX=%NACL_PLATFORM_DIR%\third_party\pymox + +:: Run the included copy of scons. +python -O -OO "%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\script\scons" ^ +--file=build.scons ^ +--site-dir="%NACL_PLATFORM_DIR%\build_tools\nacl_sdk_scons" %* diff --git a/native_client_sdk/src/project_templates/test.scons b/native_client_sdk/src/project_templates/test.scons new file mode 100644 index 0000000..c00e2a8 --- /dev/null +++ b/native_client_sdk/src/project_templates/test.scons @@ -0,0 +1,26 @@ +#! -*- python -*- +# +# Copyright (c) 2010 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Build file for tests of init_project interface. + +Adapted from scons documentation: http://www.scons.org/wiki/UnitTests + +RunUnitTests(): Runs a comment and uses the return code to determine success. +""" + +__author__ = 'mlinck@google.com (Michael Linck)' + +import os +import sys + +Import('env') + +#TODO(mlinck) Enable this test again when it works on Windows +cmd = env.CreatePythonUnitTest(filename='init_project_test.py', + dependencies=['init_project.py'], + disabled=env['IS_WINDOWS']) + +env.AddNodeToTestSuite(cmd, ['bot'], 'run_init_project_test', 'small') diff --git a/native_client_sdk/src/project_templates/vs/project_file.sln b/native_client_sdk/src/project_templates/vs/project_file.sln new file mode 100644 index 0000000..05f5169 --- /dev/null +++ b/native_client_sdk/src/project_templates/vs/project_file.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "<PROJECT_NAME>", "<PROJECT_NAME>.vcproj", "{<VS_PROJECT_UUID>}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {<VS_PROJECT_UUID>}.Debug|Win32.ActiveCfg = Debug|Win32 + {<VS_PROJECT_UUID>}.Debug|Win32.Build.0 = Debug|Win32 + {<VS_PROJECT_UUID>}.Release|Win32.ActiveCfg = Release|Win32 + {<VS_PROJECT_UUID>}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/native_client_sdk/src/project_templates/vs/project_file.vcproj b/native_client_sdk/src/project_templates/vs/project_file.vcproj new file mode 100644 index 0000000..d2b0f25 --- /dev/null +++ b/native_client_sdk/src/project_templates/vs/project_file.vcproj @@ -0,0 +1,184 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9.00" + Name="<PROJECT_NAME>" + ProjectGUID="{<VS_PROJECT_UUID>" + RootNamespace="<PROJECT_NAME>" + Keyword="MakeFileProj" + TargetFrameworkVersion="196613" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="0" + > + <Tool + Name="VCNMakeTool" + BuildCommandLine="$(NACL_SDK_ROOT)\<NACL_PLATFORM>\examples\scons" + ReBuildCommandLine="$(NACL_SDK_ROOT)\<NACL_PLATFORM>\examples\scons -c && $(NACL_SDK_ROOT)\<NACL_PLATFORM>\examples\scons" + CleanCommandLine="$(NACL_SDK_ROOT)\<NACL_PLATFORM>\examples\scons -c" + Output="<PROJECT_NAME>_x86_64_dbg.nexe" + PreprocessorDefinitions="WIN32;_DEBUG" + IncludeSearchPath="" + ForcedIncludes="" + AssemblySearchPath="" + ForcedUsingAssemblies="" + CompileAsManaged="" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="0" + > + <Tool + Name="VCNMakeTool" + BuildCommandLine="" + ReBuildCommandLine="" + CleanCommandLine="" + Output="<PROJECT_NAME>.exe" + PreprocessorDefinitions="WIN32;NDEBUG" + IncludeSearchPath="" + ForcedIncludes="" + AssemblySearchPath="" + ForcedUsingAssemblies="" + CompileAsManaged="" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{<VS_SOURCE_UUID>}" + > + <File + RelativePath=".\build.scons" + > + </File> + <File + RelativePath=".\<PROJECT_NAME>.cc" + > + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{<VS_HEADER_UUID>}" + > + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav" + UniqueIdentifier="{<VS_RESOURCE_UUID>}" + > + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9.00" + Name="<PROJECT_NAME>" + ProjectGUID="{<VS_PROJECT_UUID>" + RootNamespace="<PROJECT_NAME>" + Keyword="MakeFileProj" + TargetFrameworkVersion="196613" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="0" + > + <Tool + Name="VCNMakeTool" + BuildCommandLine="$(NACL_SDK_ROOT)\<NACL_PLATFORM>\examples\scons" + ReBuildCommandLine="$(NACL_SDK_ROOT)\<NACL_PLATFORM>\examples\scons -c && $(NACL_SDK_ROOT)\<NACL_PLATFORM>\examples\scons" + CleanCommandLine="$(NACL_SDK_ROOT)\<NACL_PLATFORM>\examples\scons -c" + Output="<PROJECT_NAME>_x86_64_dbg.nexe" + PreprocessorDefinitions="WIN32;_DEBUG" + IncludeSearchPath="" + ForcedIncludes="" + AssemblySearchPath="" + ForcedUsingAssemblies="" + CompileAsManaged="" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="0" + > + <Tool + Name="VCNMakeTool" + BuildCommandLine="" + ReBuildCommandLine="" + CleanCommandLine="" + Output="<PROJECT_NAME>.exe" + PreprocessorDefinitions="WIN32;NDEBUG" + IncludeSearchPath="" + ForcedIncludes="" + AssemblySearchPath="" + ForcedUsingAssemblies="" + CompileAsManaged="" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{<VS_SOURCE_UUID>}" + > + <File + RelativePath=".\build.scons" + > + </File> + <File + RelativePath=".\<PROJECT_NAME>.cc" + > + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{<VS_HEADER_UUID>}" + > + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav" + UniqueIdentifier="{<VS_RESOURCE_UUID>}" + > + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/native_client_sdk/src/scons b/native_client_sdk/src/scons new file mode 100755 index 0000000..c14f666 --- /dev/null +++ b/native_client_sdk/src/scons @@ -0,0 +1,37 @@ +#!/bin/bash +# +# Copyright (c) 2011 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +readonly SCRIPT_DIR="$(dirname "$0")" +readonly SCRIPT_DIR_ABS="$(cd "${SCRIPT_DIR}" ; pwd -P)" + +# Use the batch file as an entry point if on cygwin. +if [ "x${OSTYPE}" = "xcygwin" ]; then + # Use extended globbing (cygwin should always have it). + shopt -s extglob + # Filter out cygwin python (everything under /usr or /bin, or *cygwin*). + export PATH=${PATH/#\/bin*([^:])/} + export PATH=${PATH//:\/bin*([^:])/} + export PATH=${PATH/#\/usr*([^:])/} + export PATH=${PATH//:\/usr*([^:])/} + export PATH=${PATH/#*([^:])cygwin*([^:])/} + export PATH=${PATH//:*([^:])cygwin*([^:])/} + "${SCRIPT_DIR_ABS}/scons.bat" $* + exit +fi + +readonly BASE_SCRIPT="${SCRIPT_DIR_ABS}/third_party/scons-2.0.1/script/scons" + +export NACL_SDK_ROOT="${SCRIPT_DIR_ABS}" + +export SCONS_LIB_DIR="${NACL_SDK_ROOT}/third_party/scons-2.0.1/engine" +export PYTHONPATH="${SCRIPT_DIR_ABS}/third_party/scons-2.0.1/engine:${SCRIPT_DIR_ABS}/third_party/native_client/native_client/build" +# We have to do this because scons overrides PYTHONPATH and does not preserve +# what is provided by the OS. The custom variable name won't be overwritten. +export PYMOX="${NACL_SDK_ROOT}/third_party/pymox" + +"${BASE_SCRIPT}" --file=main.scons $* + diff --git a/native_client_sdk/src/scons.bat b/native_client_sdk/src/scons.bat new file mode 100755 index 0000000..0a6d3db --- /dev/null +++ b/native_client_sdk/src/scons.bat @@ -0,0 +1,28 @@ +@echo off
+
+:: Copyright (c) 2011 The Native Client Authors. All rights reserved.
+:: Use of this source code is governed by a BSD-style license that can be
+:: found in the LICENSE file.
+
+setlocal
+
+set NACL_SDK_ROOT=%~dp0
+
+:: Preserve a copy of the PATH (in case we need it later, mainly for cygwin).
+set PRESCONS_PATH=%PATH%
+
+:: Set the PYTHONPATH and SCONS_LIB_DIR so we can import SCons modules
+set SCONS_LIB_DIR=%~dp0third_party\scons-2.0.1\engine
+set PYTHONPATH=%~dp0third_party\scons-2.0.1\engine;%~dp0third_party\native_client\native_client\build
+
+:: We have to do this because scons overrides PYTHONPATH and does not preserve
+:: what is provided by the OS. The custom variable name won't be overwritten.
+set PYMOX=%~dp0third_party\pymox
+
+:: Stop incessant CYGWIN complains about "MS-DOS style path"
+set CYGWIN=nodosfilewarning %CYGWIN%
+
+:: Run the included copy of scons.
+python -O -OO "%~dp0third_party\scons-2.0.1\script\scons" --file=main.scons %*
+
+:end
|