Add libcody
In order to separate compiler from build system, C++ Modules, as implemented in GCC introduces a communication channel between those two entities. This is implemented by libcody. It is anticipated that other implementations will also implement this protocol, or use libcody to provide it. * Makefile.def: Add libcody. * configure.ac: Add libcody. * Makefile.in: Regenerated. * configure: Regenerated. gcc/ * Makefile.in (CODYINC, CODYLIB, CODYLIB_H): New. Use them. libcody/ * configure.ac: New. * CMakeLists.txt: New. * CODING.md: New. * CONTRIB.md: New. * LICENSE: New. * LICENSE.gcc: New. * Makefile.in: New. * Makesub.in: New. * README.md: New. * buffer.cc: New. * build-aux/config.guess: New. * build-aux/config.sub: New. * build-aux/install-sh: New. * client.cc: New. * cmake/libcody-config-ix.cmake * cody.hh: New. * config.h.in: New. * config.m4: New. * configure: New. * configure.ac: New. * dox.cfg.in: New. * fatal.cc: New. * gdbinit.in: New. * internal.hh: New. * netclient.cc: New. * netserver.cc: New. * packet.cc: New. * resolver.cc: New. * server.cc: New. * tests/01-serialize/connect.cc: New. * tests/01-serialize/decoder.cc: New. * tests/01-serialize/encoder.cc: New. * tests/02-comms/client-1.cc: New. * tests/02-comms/pivot-1.cc: New. * tests/02-comms/server-1.cc: New. * tests/Makesub.in: New. * tests/jouster: New.
This commit is contained in:
parent
c5271279d6
commit
362303298a
10
Makefile.def
10
Makefile.def
@ -81,6 +81,15 @@ host_modules= { module= itcl; };
|
||||
host_modules= { module= ld; bootstrap=true; };
|
||||
host_modules= { module= libbacktrace; bootstrap=true; };
|
||||
host_modules= { module= libcpp; bootstrap=true; };
|
||||
// As with libiconv, don't install any of libcody
|
||||
host_modules= { module= libcody; bootstrap=true;
|
||||
no_install= true;
|
||||
missing= pdf;
|
||||
missing= html;
|
||||
missing= info;
|
||||
missing= install-pdf;
|
||||
missing= install-html;
|
||||
missing= install-info; };
|
||||
host_modules= { module= libdecnumber; bootstrap=true; };
|
||||
host_modules= { module= libgui; };
|
||||
host_modules= { module= libiberty; bootstrap=true;
|
||||
@ -347,6 +356,7 @@ dependencies = { module=all-gcc; on=all-build-libcpp; };
|
||||
dependencies = { module=all-gcc; on=all-zlib; };
|
||||
dependencies = { module=all-gcc; on=all-libbacktrace; hard=true; };
|
||||
dependencies = { module=all-gcc; on=all-libcpp; hard=true; };
|
||||
dependencies = { module=all-gcc; on=all-libcody; hard=true; };
|
||||
dependencies = { module=all-gcc; on=all-libdecnumber; hard=true; };
|
||||
dependencies = { module=all-gcc; on=all-libiberty; };
|
||||
dependencies = { module=all-gcc; on=all-fixincludes; };
|
||||
|
1146
Makefile.in
1146
Makefile.in
File diff suppressed because it is too large
Load Diff
2
configure
vendored
2
configure
vendored
@ -2787,7 +2787,7 @@ build_tools="build-texinfo build-flex build-bison build-m4 build-fixincludes"
|
||||
|
||||
# these libraries are used by various programs built for the host environment
|
||||
#f
|
||||
host_libs="intl libiberty opcodes bfd readline tcl tk itcl libgui zlib libbacktrace libcpp libdecnumber gmp mpfr mpc isl libelf libiconv libctf"
|
||||
host_libs="intl libiberty opcodes bfd readline tcl tk itcl libgui zlib libbacktrace libcpp libcody libdecnumber gmp mpfr mpc isl libelf libiconv libctf"
|
||||
|
||||
# these tools are built for the host environment
|
||||
# Note, the powerpc-eabi build depends on sim occurring before gdb in order to
|
||||
|
@ -132,7 +132,7 @@ build_tools="build-texinfo build-flex build-bison build-m4 build-fixincludes"
|
||||
|
||||
# these libraries are used by various programs built for the host environment
|
||||
#f
|
||||
host_libs="intl libiberty opcodes bfd readline tcl tk itcl libgui zlib libbacktrace libcpp libdecnumber gmp mpfr mpc isl libelf libiconv libctf"
|
||||
host_libs="intl libiberty opcodes bfd readline tcl tk itcl libgui zlib libbacktrace libcpp libcody libdecnumber gmp mpfr mpc isl libelf libiconv libctf"
|
||||
|
||||
# these tools are built for the host environment
|
||||
# Note, the powerpc-eabi build depends on sim occurring before gdb in order to
|
||||
|
@ -412,6 +412,9 @@ enable_as_accelerator = @enable_as_accelerator@
|
||||
CPPLIB = ../libcpp/libcpp.a
|
||||
CPPINC = -I$(srcdir)/../libcpp/include
|
||||
|
||||
CODYLIB = ../libcody/libcody.a
|
||||
CODYINC = -I$(srcdir)/../libcody
|
||||
|
||||
# Where to find decNumber
|
||||
enable_decimal_float = @enable_decimal_float@
|
||||
DECNUM = $(srcdir)/../libdecnumber
|
||||
@ -982,6 +985,7 @@ SYSTEM_H = system.h hwint.h $(srcdir)/../include/libiberty.h \
|
||||
PREDICT_H = predict.h predict.def
|
||||
CPPLIB_H = $(srcdir)/../libcpp/include/line-map.h \
|
||||
$(srcdir)/../libcpp/include/cpplib.h
|
||||
CODYLIB_H = $(srcdir)/../libcody/cody.hh
|
||||
INPUT_H = $(srcdir)/../libcpp/include/line-map.h input.h
|
||||
OPTS_H = $(INPUT_H) $(VEC_H) opts.h $(OBSTACK_H)
|
||||
SYMTAB_H = $(srcdir)/../libcpp/include/symtab.h $(OBSTACK_H)
|
||||
@ -1102,7 +1106,7 @@ BUILD_ERRORS = build/errors.o
|
||||
# libintl.h will be found in ../intl if we are using the included libintl.
|
||||
INCLUDES = -I. -I$(@D) -I$(srcdir) -I$(srcdir)/$(@D) \
|
||||
-I$(srcdir)/../include @INCINTL@ \
|
||||
$(CPPINC) $(GMPINC) $(DECNUMINC) $(BACKTRACEINC) \
|
||||
$(CPPINC) $(CODYINC) $(GMPINC) $(DECNUMINC) $(BACKTRACEINC) \
|
||||
$(ISLINC)
|
||||
|
||||
COMPILE.base = $(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) -o $@
|
||||
@ -1714,7 +1718,7 @@ endif
|
||||
ALL_HOST_OBJS = $(ALL_HOST_FRONTEND_OBJS) $(ALL_HOST_BACKEND_OBJS)
|
||||
|
||||
BACKEND = libbackend.a main.o libcommon-target.a libcommon.a \
|
||||
$(CPPLIB) $(LIBDECNUMBER)
|
||||
$(CPPLIB) $(CODYLIB) $(LIBDECNUMBER)
|
||||
|
||||
# This is defined to "yes" if Tree checking is enabled, which roughly means
|
||||
# front-end checking.
|
||||
|
121
libcody/CMakeLists.txt
Normal file
121
libcody/CMakeLists.txt
Normal file
@ -0,0 +1,121 @@
|
||||
# Top Level CMake file for libcody.
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.3)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
message(STATUS "No build type selected, default to MinSizeRel")
|
||||
set(CMAKE_BUILD_TYPE MinSizeRel)
|
||||
set(LIBCODY_ENABLE_ASSERTIONS 1)
|
||||
endif()
|
||||
string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE)
|
||||
|
||||
set(cmake_3_2_USES_TERMINAL USES_TERMINAL)
|
||||
|
||||
if( CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR AND NOT MSVC_IDE)
|
||||
message(FATAL_ERROR "In-source builds are not allowed. ")
|
||||
endif()
|
||||
|
||||
# message(STATUS "SRC ${CMAKE_SOURCE_DIR} CSRC : ${CMAKE_CURRENT_SOURCE_DIR} ")
|
||||
|
||||
# Add path for custom modules
|
||||
set(CMAKE_MODULE_PATH
|
||||
${CMAKE_MODULE_PATH}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
|
||||
|
||||
# If we are building stand-alone, set up the names and versions.
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR
|
||||
OR LIBCODY_STANDALONE_BUILD)
|
||||
|
||||
project(libcody CXX)
|
||||
|
||||
set(PACKAGE_NAME codylib)
|
||||
set(LIBCODY_VERSION_MAJOR 0)
|
||||
set(LIBCODY_VERSION_MINOR 0)
|
||||
set(LIBCODY_VERSION_PATCH 1)
|
||||
set(LIBCODY_VERSION_SUFFIX git)
|
||||
set(LIBCODY_VERSION "${LIBCODY_VERSION_MAJOR}.${LIBCODY_VERSION_MINOR}.${LIBCODY_VERSION_PATCH}")
|
||||
set(PACKAGE_VERSION "${LIBCODY_VERSION}-${LIBCODY_VERSION_SUFFIX}")
|
||||
set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}")
|
||||
set(PACKAGE_URL "https://github.com/urnathan/libcody")
|
||||
set(PACKAGE_BUGREPORT "https://github.com/urnathan/libcody/issues")
|
||||
|
||||
set (GIT_REV "git" "-C" "${CMAKE_CURRENT_SOURCE_DIR}" "rev-parse" "--short=12" "HEAD")
|
||||
execute_process(
|
||||
COMMAND ${GIT_REV}
|
||||
RESULT_VARIABLE HAD_ERROR
|
||||
OUTPUT_VARIABLE CODY_REVISION
|
||||
)
|
||||
if (NOT HAD_ERROR)
|
||||
string(REGEX REPLACE "\n$" "" CODY_REVISION "${CODY_REVISION}")
|
||||
set (GIT_CHANGES "git" "-C" "${CMAKE_CURRENT_SOURCE_DIR}" "diff-index" "--quiet" "HEAD" "--")
|
||||
execute_process(
|
||||
COMMAND ${GIT_CHANGES}
|
||||
RESULT_VARIABLE MOD_ERROR
|
||||
OUTPUT_VARIABLE MOD_OUTPUT
|
||||
)
|
||||
if (MOD_ERROR)
|
||||
set (CODY_REVISION "${CODY_REVISION}-modified")
|
||||
endif ()
|
||||
else()
|
||||
set(CODY_REVISION, "unknown")
|
||||
endif ()
|
||||
set(LIBCODY_STANDALONE YES)
|
||||
else()
|
||||
set(LIBCODY_STANDALONE NO)
|
||||
endif()
|
||||
|
||||
# We are using C++11
|
||||
set (CMAKE_CXX_STANDARD 11)
|
||||
|
||||
message(STATUS "git revision ${CODY_REVISION} ")
|
||||
option(CODY_CHECKING "Enable checking" ON)
|
||||
# Address github issue #10
|
||||
option(CODY_WITHEXCEPTIONS "Enable exceptions" OFF)
|
||||
|
||||
if (LIBCODY_STANDALONE)
|
||||
include(CTest)
|
||||
endif()
|
||||
|
||||
include(libcody-config-ix)
|
||||
|
||||
add_definitions(
|
||||
-DPACKAGE_URL="${PACKAGE_URL}"
|
||||
-DBUGURL="${PACKAGE_BUGREPORT}"
|
||||
-DSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
-DPACKAGE_NAME="${PACKAGE_NAME}"
|
||||
-DPACKAGE_STRING="${PACKAGE_STRING}"
|
||||
-DPACKAGE_VERSION="${LIBCODY_VERSION}"
|
||||
-DREVISION="${CODY_REVISION}"
|
||||
)
|
||||
if (CODY_CHECKING)
|
||||
add_definitions(-DNMS_CHECKING=1)
|
||||
else()
|
||||
add_definitions(-DNMS_CHECKING=0)
|
||||
endif()
|
||||
|
||||
set(LIBCODY_SOURCES
|
||||
buffer.cc
|
||||
client.cc
|
||||
fatal.cc
|
||||
netclient.cc
|
||||
netserver.cc
|
||||
resolver.cc
|
||||
packet.cc
|
||||
server.cc)
|
||||
|
||||
if(LIBCODY_STANDALONE)
|
||||
add_library(cody STATIC ${LIBCODY_SOURCES})
|
||||
else()
|
||||
message(STATUS "Configured for in-tree build of libcody as LLVMcody")
|
||||
add_llvm_component_library(LLVMcody ${LIBCODY_SOURCES})
|
||||
endif()
|
||||
|
||||
if (LIBCODY_STANDALONE)
|
||||
|
||||
set_target_properties(cody PROPERTIES PUBLIC_HEADER "cody.hh")
|
||||
install(TARGETS cody
|
||||
LIBRARY DESTINATION lib
|
||||
PUBLIC_HEADER DESTINATION include
|
||||
)
|
||||
endif()
|
115
libcody/CODING.md
Normal file
115
libcody/CODING.md
Normal file
@ -0,0 +1,115 @@
|
||||
# Coding standard
|
||||
|
||||
I guess I should document this, it might not be obvious.
|
||||
|
||||
libcody is implemented in C++11. Because it's used in compiler
|
||||
development, we can't use the latest and greatest.
|
||||
|
||||
The formatting is close to GNU, but with a few differences.
|
||||
|
||||
## Extensions to C++11
|
||||
|
||||
It uses __VA_OPT__ when available, falling back on GNU's variadic
|
||||
macro `,#` extension. This is in the `Assert` macro, so one can have
|
||||
multi-argument template instantiations there. Not that libcody does
|
||||
that, but this is code I used elsewhere.
|
||||
|
||||
## GNU
|
||||
|
||||
The underlying formatting is GNU style. Here are a few notes about
|
||||
things that commonly catches programmers unfamiliar with it is:
|
||||
|
||||
* Spaces between binary operators. Particularly in a function call,
|
||||
between the name and the open paren:
|
||||
|
||||
```c++
|
||||
Fn (a + b, ary[4], *ptr);
|
||||
```
|
||||
|
||||
In general GNU style uses a lot more whitespace than Clang-style.
|
||||
We're not trying to cram as much code as possible onto a page!
|
||||
|
||||
* Scope braces are always on a line of their own, indented by 2
|
||||
spaces, if they're a sub-statement of an `if`, `for` or whatever:
|
||||
|
||||
```c++
|
||||
if (bob)
|
||||
{
|
||||
Frob ();
|
||||
Quux ();
|
||||
}
|
||||
```
|
||||
|
||||
Conditions and loops containing a single statement should not use `{}`.
|
||||
FWIW this was my personal indentation scheme, before I even met GNU code!
|
||||
|
||||
* The same is true for a function definition body, except the
|
||||
indentation is zero:
|
||||
|
||||
```c++
|
||||
int Foo ()
|
||||
noexcept // indented
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
* Initialization bracing is not like scope bracing. There tends to be
|
||||
more flexibility.
|
||||
|
||||
* Break lines at 80 chars, this should be /before/ the operator, not after:
|
||||
|
||||
```c++
|
||||
a = (b
|
||||
+ c);
|
||||
ptr
|
||||
->MemberFn (stuff);
|
||||
Func
|
||||
(arg);
|
||||
```
|
||||
|
||||
Thus you can tell what lines are continued from the previous by
|
||||
looking at their start. Use parens to control indentation.
|
||||
|
||||
If you find yourself wanting to break a line at `.`, don't.
|
||||
Refactor your code to avoid needing that.
|
||||
|
||||
* Template instantiations and C++ casts should have no space before the `<`:
|
||||
|
||||
```c++
|
||||
std::vector<int> k;
|
||||
static_cast<T> (arg); // space before the ( though
|
||||
```
|
||||
|
||||
* Pointer and reference types need a space before the `*` or `&`, if
|
||||
the preceding token is ascii text (a cpp-identifier):
|
||||
|
||||
```
|
||||
int *ptr;
|
||||
int **ptr_ptr;
|
||||
int *&pref = ptr;
|
||||
```
|
||||
|
||||
See below a difference in qualifier placement.
|
||||
|
||||
* Code should compile without warnings.
|
||||
|
||||
## Not GNU
|
||||
|
||||
### Names
|
||||
|
||||
Unlike GNU code, variants of Camel Case are used. use `PascalCase`
|
||||
for function, type and global variable names. Use `dromedaryCase` for
|
||||
member variables. Block-scope vars can be `dromedaryCase` or
|
||||
`snake_case`, your choice.
|
||||
|
||||
### Type qualifiers
|
||||
|
||||
Type qualifiers go after the thing they qualify. You have to do this
|
||||
for pointers anyway, and read them inside-out, because, C Just being
|
||||
consistent:
|
||||
|
||||
```c++
|
||||
int const foo = 5; // constant int
|
||||
int *const pfoo = nullptr; // constant pointer to int
|
||||
```
|
10
libcody/CONTRIB.md
Normal file
10
libcody/CONTRIB.md
Normal file
@ -0,0 +1,10 @@
|
||||
# A probably incomplete list of contributors
|
||||
|
||||
Thanks for the interest in this library!
|
||||
|
||||
* Iain Sandoe
|
||||
Darwin testing and porting
|
||||
|
||||
* Martin Liska
|
||||
Code cleanups
|
||||
|
201
libcody/LICENSE
Normal file
201
libcody/LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
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.
|
29
libcody/LICENSE.gcc
Normal file
29
libcody/LICENSE.gcc
Normal file
@ -0,0 +1,29 @@
|
||||
This instance of Libcody is licensed under the GPLv3 as part of
|
||||
GCC. (See ../COPYING3.)
|
||||
|
||||
The documentation of this file itself is separate from libcody.
|
||||
|
||||
The GCC steering committee have declined to decide whether libcody may
|
||||
be included in GCC as an external dependency licensed under the Apache
|
||||
v2 license.
|
||||
|
||||
In order to progress C++20 modules, this instance is relicensed as
|
||||
part of GCC under the GPLv3 and assigned to the FSF. This version is
|
||||
derived from libcody upstream b79dbea with contributions from jjravi
|
||||
removed. Specifically the bulk of the code is authored by Nathan
|
||||
Sidwell (me), and some portability issues fixed by Iain Sandoe. Iain
|
||||
has blessed this contribution in a private communication to me. A
|
||||
trivial install fix from Johel Peña is included here, along with typo
|
||||
fixes to README.md from Boris Kolpackov (I do not think either of
|
||||
those contains copyrightable work).
|
||||
|
||||
To the extent that Apache V2 prevents it, you may not update to other
|
||||
upstream versions (past or future) of libcody and relicensing and/or
|
||||
reassigning copyright of that update without reobtaining the authors'
|
||||
permission. You may of course update to other versions of libcody
|
||||
but keep the Apache license and libcody's copyright assignment. [This
|
||||
is merely pointing out the implications of the Apache V2 license and
|
||||
libcody's copyright, not adding additional requirements.]
|
||||
|
||||
Any patches applied to this instance should be provided to upstream
|
||||
libcody, for consideration there.
|
187
libcody/Makefile.in
Normal file
187
libcody/Makefile.in
Normal file
@ -0,0 +1,187 @@
|
||||
# Nathan's generic Makefile -*- mode:Makefile -*-
|
||||
# Copyright (C) 2019-2020 Nathan Sidwell, nathan@acm.org
|
||||
# License: Apache v2.0
|
||||
|
||||
ifeq (0,$(MAKELEVEL))
|
||||
ifneq (,@tools@)
|
||||
$(info Prepending @tools@/bin to PATH)
|
||||
PATH := @tools@/bin:$(PATH)
|
||||
export PATH
|
||||
endif
|
||||
ifeq (,$(SERIAL))
|
||||
# Figure out if we should set parallelism
|
||||
ifeq (,$(filter clean%,$(MAKECMDGOALS)))
|
||||
PARALLELISM := @NUM_CPUS@
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq (00,$(MAKELEVEL)$(if $(PARALLELISM),0,1))
|
||||
# Although Make 4.3 documentation suggests I can set parallelism just
|
||||
# by appending to MAKEFLAGS, it doesn't seem to work. It's also not
|
||||
# possible to figure out the current Make invocation's parallelism,
|
||||
# the -j option doesn't appear in MAKEFLAGS and is magically inserted
|
||||
# when that is expanded in a rule. I can't figure how to get a rule
|
||||
# expansion into a variable to test. Fortunately, Make propagates an
|
||||
# incoming -j option rather than the one you attempted to append
|
||||
$(info Parallelizing $(PARALLELISM) ways)
|
||||
MAKEFLAGS += -j$(PARALLELISM)
|
||||
ifneq (,$(MAKECMDGOALS))
|
||||
$(MAKECMDGOALS): recurse
|
||||
endif
|
||||
recurse:
|
||||
$(MAKE) -r$(MAKEFLAGS) $(MAKECMDGOALS)
|
||||
.PHONY: recurse
|
||||
else
|
||||
|
||||
srcdir := @srcdir@
|
||||
prefix := @prefix@
|
||||
exec_prefix := @exec_prefix@
|
||||
bindir := @bindir@
|
||||
libdir := @libdir@
|
||||
includedir := @includedir@
|
||||
SUBDIRS := @SUBDIRS@
|
||||
# autoconf doesn't seem to like setting SHELL
|
||||
SHELL := $(shell which zsh 2>/dev/null >/dev/null && echo zsh \
|
||||
|| (echo "No ZSH, maybe flakey" >&2 && echo sh))
|
||||
|
||||
# We have to place the -I paths last, so that building will see -I paths to us
|
||||
CXX := $(filter-out -I%,@CXX@)
|
||||
AR := @AR@
|
||||
INSTALL := $(srcdir)/build-aux/install-sh
|
||||
|
||||
# C++ compiler options
|
||||
CXXFLAGS := @CXXFLAGS@
|
||||
CXXINC := $(filter -I%,@CXX@)
|
||||
CXXOPTS := $(CXXFLAGS)
|
||||
ifeq ($(notdir $(firstword $(CXX))),g++)
|
||||
# It's GCC, or pretending to be it -- so it better smell like it!
|
||||
# Code generation
|
||||
CXXOPTS += -fno-enforce-eh-specs
|
||||
CXXOPTS += -fno-stack-protector -fno-threadsafe-statics
|
||||
ifneq (@EXCEPTIONS@,yes)
|
||||
CXXOPTS += -fno-exceptions -fno-rtti
|
||||
endif
|
||||
ifeq ($(filter -fdebug-prefix-map=%,$(CXXOPTS)),)
|
||||
CXXOPTS += -fdebug-prefix-map=${srcdir}/=
|
||||
endif
|
||||
# Warning options
|
||||
CXXOPTS += -W -Wall -Woverloaded-virtual -Wshadow
|
||||
CXXOPTS += -Wno-invalid-offsetof -Wno-unused-variable
|
||||
CXXOPTS += -Wno-missing-field-initializers
|
||||
# Diagnostic options, look at controlling terminal so that piping
|
||||
# through more works
|
||||
MLEN := $(shell stty size </dev/tty 2>/dev/null | cut -d' ' -f2)
|
||||
ifneq (,$(MLEN))
|
||||
CXXOPTS += -fmessage-length=$(MLEN)
|
||||
endif
|
||||
CXXOPTS += -fdiagnostics-color=always -fno-diagnostics-show-option
|
||||
else
|
||||
ifeq ($(notdir $(firstword $(CXX))),clang++)
|
||||
CXXOPTS += -fno-stack-protector -fno-threadsafe-statics
|
||||
ifneq (@EXCEPTIONS@,yes)
|
||||
CXXOPTS += -fno-exceptions -fno-rtti
|
||||
endif
|
||||
# Warning options
|
||||
CXXOPTS += -W -Wall -Woverloaded-virtual -Wshadow
|
||||
CXXOPTS += -Wno-invalid-offsetof -Wno-unused-variable
|
||||
CXXOPTS += -Wno-missing-field-initializers
|
||||
else
|
||||
# Add different compiler's options here
|
||||
endif
|
||||
endif
|
||||
|
||||
# Config
|
||||
CXXOPTS += $(filter-out -DHAVE_CONFIG_H,@DEFS@) -include config.h
|
||||
|
||||
# Linker options
|
||||
LDFLAGS := -L. @LDFLAGS@
|
||||
LIBS := @LIBS@
|
||||
|
||||
# Per-source & per-directory compile flags (warning: recursive)
|
||||
SRC_CXXFLAGS = $(CXXFLAGS$(patsubst $(srcdir)%,%,$1)) \
|
||||
$(if $(filter-out $(srcdir)/,$1),\
|
||||
$(call $0,$(dir $(patsubst %/,%,$1))))
|
||||
|
||||
ifneq ($(MAINTAINER),)
|
||||
override MAINTAINER += $1
|
||||
endif
|
||||
ifeq (@MAINTAINER@,yes)
|
||||
MAINTAINER = $2
|
||||
else
|
||||
MAINTAINER = \# --enable-maintainer-mode to rebuild $1, or make MAINTAINER=touch
|
||||
endif
|
||||
|
||||
vpath %.in $(srcdir)
|
||||
vpath %.cc $(srcdir)
|
||||
|
||||
.SUFFIXES: .o .cc
|
||||
|
||||
%.o: %.cc
|
||||
@mkdir -p $(dir $@)
|
||||
$(CXX) $(strip $(CXXOPTS) $(call SRC_CXXFLAGS,$<) $(CXXINC)) \
|
||||
-MMD -MP -MF ${@:.o=.d} -c -o $@ $<
|
||||
|
||||
all:: Makefile $(addprefix all.,$(SUBDIRS))
|
||||
|
||||
check:: Makefile $(addprefix check.,$(SUBDIRS))
|
||||
|
||||
clean:: Makefile $(addprefix clean.,$(SUBDIRS))
|
||||
|
||||
revision.stamp: $(addprefix $(srcdir)/,. $(SUBDIRS))
|
||||
@revision=$$(git -C $(srcdir) rev-parse HEAD 2>/dev/null) ;\
|
||||
if test -n "$$revision" ;\
|
||||
then revision=git-$$revision ;\
|
||||
if git -C $(srcdir) status --porcelain 2>/dev/null | grep -vq '^ ' ;\
|
||||
then revision+=M ;\
|
||||
fi ;\
|
||||
else revision=unknown ;\
|
||||
fi ;\
|
||||
echo $$revision > $@
|
||||
|
||||
revision: revision.stamp
|
||||
@cmp -s $< $@ || cp -f $< $@
|
||||
|
||||
clean::
|
||||
rm -f revision.stamp revision
|
||||
|
||||
distclean:: clean
|
||||
rm -f config.log config.status
|
||||
rm -rf $(SUBDIRS)
|
||||
|
||||
$(srcdir)/configure: $(srcdir)/configure.ac \
|
||||
$(patsubst %,$(srcdir)/%/config.m4,. $(SUBDIRS))
|
||||
$(call MAINTAINER,$@,cd $(@D) && autoconf -W all,error)
|
||||
|
||||
$(srcdir)/config.h.in: $(srcdir)/configure.ac \
|
||||
$(patsubst %,$(srcdir)/%/config.m4,. $(SUBDIRS))
|
||||
$(call MAINTAINER,$@,cd $(@D) && autoheader -f -W all,error)
|
||||
|
||||
config.h: config.status config.h.in
|
||||
./$< --header=$@
|
||||
touch $@
|
||||
|
||||
ifeq ($(filter %clean,$(MAKECMDGOALS)),)
|
||||
@CONFIG_FILES@: %: config.status %.in
|
||||
./$< --file=$@
|
||||
touch $@
|
||||
endif
|
||||
|
||||
config.status: $(srcdir)/configure $(srcdir)/config.h.in
|
||||
if test -x $@; then ./$@ -recheck; else $< @configure_args@; fi
|
||||
|
||||
distclean:: clean
|
||||
rm -f config.h
|
||||
|
||||
maintainer-clean:: distclean
|
||||
rm -f $(srcdir)/config.h.in
|
||||
|
||||
clean::
|
||||
rm -f $(shell find $(srcdir) -name '*~')
|
||||
|
||||
.PHONY: all check clean distclean maintainer-clean
|
||||
|
||||
-include $(addsuffix /Makesub,. $(SUBDIRS))
|
||||
-include $(addsuffix /tests/Makesub,. $(SUBDIRS))
|
||||
|
||||
endif
|
48
libcody/Makesub.in
Normal file
48
libcody/Makesub.in
Normal file
@ -0,0 +1,48 @@
|
||||
# CODYlib -*- mode:Makefile -*-
|
||||
# Copyright (C) 2019-2020 Nathan Sidwell, nathan@acm.org
|
||||
# License: Apache v2.0
|
||||
|
||||
DOXYGEN := @DOXYGEN@
|
||||
CXXFLAGS/ := -I$(srcdir)
|
||||
LIBCODY.O := buffer.o client.o fatal.o netclient.o netserver.o \
|
||||
resolver.o packet.o server.o
|
||||
|
||||
all:: .gdbinit
|
||||
|
||||
.gdbinit: gdbinit
|
||||
rm -f $@ ; ln -s $< $@
|
||||
|
||||
clean::
|
||||
rm -f gdbinit .gdbinit
|
||||
|
||||
all:: libcody.a
|
||||
|
||||
libcody.a: $(LIBCODY.O)
|
||||
$(AR) -cr $@ $^
|
||||
|
||||
clean::
|
||||
rm -f $(LIBCODY.O) $(LIBCODY.O:.o=.d)
|
||||
rm -f libcody.a
|
||||
|
||||
CXXFLAGS/fatal.cc = -DREVISION='"$(shell cat revision)"' -DSRCDIR='"$(srcdir)"'
|
||||
|
||||
fatal.o: Makefile revision
|
||||
|
||||
doc:: dox.cfg
|
||||
ifneq ($(DOXYGEN),:)
|
||||
cd $(<D); $(DOXYGEN) $(<F) >&dox.log
|
||||
else
|
||||
@echo "doxygen not present, documentation not built"
|
||||
endif
|
||||
|
||||
clean::
|
||||
rm -rf dox dox.log
|
||||
|
||||
install::
|
||||
$(INSTALL) -d $(libdir) $(includedir)
|
||||
$(INSTALL) libcody.a $(libdir)
|
||||
$(INSTALL) $(srcdir)/cody.hh $(includedir)
|
||||
|
||||
ifeq ($(filter clean%,$(MAKECMDGOALS)),)
|
||||
-include $(LIBCODY.O:.o=.d)
|
||||
endif
|
497
libcody/README.md
Normal file
497
libcody/README.md
Normal file
@ -0,0 +1,497 @@
|
||||
# libCODY: COmpiler DYnamism<sup><a href="#1">1</a></sup>
|
||||
|
||||
Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
|
||||
libCODY is an implementation of a communication protocol between
|
||||
compilers and build systems.
|
||||
|
||||
**WARNING:** This is preliminary software.
|
||||
|
||||
In addition to supporting C++modules, this may also support LTO
|
||||
requirements and could also deal with generated #include files
|
||||
and feed the compiler with prepruned include paths and whatnot. (The
|
||||
system calls involved in include searches can be quite expensive on
|
||||
some build infrastructures.)
|
||||
|
||||
* Client and Server objects
|
||||
* Direct connection for in-process use
|
||||
* Testing with Joust (that means nothing to you, doesn't it!)
|
||||
|
||||
|
||||
## Problem Being Solved
|
||||
|
||||
The origin is in C++20 modules:
|
||||
```
|
||||
import foo;
|
||||
```
|
||||
|
||||
At that import, the compiler needs<sup><a href="#2">2</a></sup> to
|
||||
load up the compiled serialization of module `foo`. Where is that
|
||||
file? Does it even exist? Unless the build system already knows the
|
||||
dependency graph, this might be a completely unknown module. Now, the
|
||||
build system knows how to build things, but it might not have complete
|
||||
information about the dependencies. The ultimate source of
|
||||
dependencies is the source code being compiled, and specifying the
|
||||
same thing in multiple places is a recipe for build skew.
|
||||
|
||||
Hence, a protocol by which a compiler can query a build system. This
|
||||
was originally described in <a
|
||||
href="https://wg21.link/p1184r1">p1184r1:A Module Mapper</a>. Along
|
||||
with a proof-of-concept hack in GNUmake, described in <a
|
||||
href="https://wg21.link/p1602">p1602:Make Me A Module</a>. The current
|
||||
implementation has evolved and an update to p1184 will be forthcoming.
|
||||
|
||||
## Packet Encoding
|
||||
|
||||
The protocol is turn-based. The compiler sends a block of one or more
|
||||
requests to the builder, then waits for a block of responses to all of
|
||||
those requests. If the builder needs to compile something to satisfy
|
||||
a request, there may be some time before the response. A builder may
|
||||
service multiple compilers concurrently, each as a separate connection.
|
||||
|
||||
When multiple requests are in a block, the responses are also in a
|
||||
block, and in corresponding order. The responses must not be
|
||||
commenced eagerly -- they must wait until the incoming block has ended
|
||||
(as mentioned above, it is turn-based). To do otherwise risks
|
||||
deadlock, as there is no requirement for a sending end of the
|
||||
communication to listen for incoming responses (or new requests) until
|
||||
it has completed sending its current block.
|
||||
|
||||
Every request has a response.
|
||||
|
||||
Requests and responses are user-readable text. It is not intended as
|
||||
a transmission medium to send large binary objects (such as compiled
|
||||
modules). It is presumed the builder and the compiler share a file
|
||||
system, for that kind of thing.<sup><a href="#3">3</a></sup>
|
||||
|
||||
Messages characters are encoded in UTF8.
|
||||
|
||||
Messages are a sequence of octets ending with a NEWLINE (0xa). The lines
|
||||
consist of a sequence of words, separated by WHITESPACE (0x20 or 0x9).
|
||||
Words themselves do not contain WHITESPACE. Lines consisting solely
|
||||
of WHITESPACE (or empty) are ignored.
|
||||
|
||||
To encode a block of multiple messages, non-final messages end with a
|
||||
single word of SEMICOLON (0x3b), immediately before the NEWLINE. Thus
|
||||
a serial connection can determine whether a block is complete without
|
||||
decoding the messages.
|
||||
|
||||
Words containing characters in the set [-+_/%.A-Za-z0-9] need not be
|
||||
quoted. Words containing characters outside that set should be
|
||||
quoted. A zero-length word may be achieved with `''`
|
||||
|
||||
Quoted words begin and end with APOSTROPHE (x27). Within the quoted
|
||||
word, BACKSLASH (x5c) is used as an escape mechanism, with the
|
||||
following meanings:
|
||||
|
||||
* \\n - NEWLINE (0xa)
|
||||
* \\t - TAB (0x9)
|
||||
* \\' - APOSTROPHE (')
|
||||
* \\\\ - BACKSLASH (\\)
|
||||
|
||||
Characters in the range [0x00, 0x20) and 0x7f are encoded with one or
|
||||
two lowercase hex characters. Octets in the range [0x80,0xff) are
|
||||
UTF8 encodings of unicode characters outside the traditional ASCII set
|
||||
and passed as such.
|
||||
|
||||
Decoding should be more relaxed. Unquoted words containing characters
|
||||
in the range [0x20,0xff] other than BACKSLASH or APOSTROPHE should be
|
||||
accepted. In a quoted sequence, `\` followed by one or two lower case
|
||||
hex characters decode to that octet. Further, words can be
|
||||
constructed from a mixture of abutted quoted and unquoted sequences.
|
||||
For instance `FOO' 'bar` would decode to the word `FOO bar`.
|
||||
|
||||
Notice that the block continuation marker of `;` is not a valid
|
||||
encoding of the word `;`, which would be `';'`.
|
||||
|
||||
It is recommended that words are separated by single SPACE characters.
|
||||
|
||||
## Messages
|
||||
|
||||
The message descriptions use `$metavariable` examples.
|
||||
|
||||
The request messages are specific to a particular action. The response
|
||||
messages are more generic, describing their value types, but not their
|
||||
meaning. Message consumers need to know the response to decode them.
|
||||
Notice the `Packet::GetRequest()` method records in response packets
|
||||
what the request being responded to was. Do not confuse this with the
|
||||
`Packet::GetCode ()` method.
|
||||
|
||||
### Responses
|
||||
|
||||
The simplest response is a single:
|
||||
|
||||
`OK`
|
||||
|
||||
This indicates the request was successful.
|
||||
|
||||
|
||||
An error response is:
|
||||
|
||||
`ERROR $message`
|
||||
|
||||
The message is a human-readable string. It indicates failure of the request.
|
||||
|
||||
Pathnames are encoded with:
|
||||
|
||||
`PATHNAME $pathname`
|
||||
|
||||
Boolean responses use:
|
||||
|
||||
`BOOL `(`TRUE`|`FALSE`)
|
||||
|
||||
### Handshake Request
|
||||
|
||||
The first message is a handshake:
|
||||
|
||||
`HELLO $version $compiler $ident`
|
||||
|
||||
The `$version` is a numeric value, currently `1`. `$compiler` identifies
|
||||
the compiler — builders may need to keep compiled modules from
|
||||
different compilers separate. `$ident` is an identifier the builder
|
||||
might use to identify the compilation it is communicating with.
|
||||
|
||||
Responses are:
|
||||
|
||||
`HELLO $version $builder [$flags]`
|
||||
|
||||
A successful handshake. The communication is now connected and other
|
||||
messages may be exchanged. An ERROR response indicates an unsuccessful
|
||||
handshake. The communication remains unconnected.
|
||||
|
||||
There is nothing restricting a handshake to its own message block. Of
|
||||
course, if the handshake fails, subsequent non-handshake messages in
|
||||
the block will fail (producing error responses).
|
||||
|
||||
The `$flags` word, if present allows a server to control what requests
|
||||
might be given. See below.
|
||||
|
||||
### C++ Module Requests
|
||||
|
||||
A set of requests are specific to C++ modules:
|
||||
|
||||
#### Flags
|
||||
|
||||
Several requests and one response have an optional `$flags` word.
|
||||
These are the `Cody::Flags` value pertaining to that request. If
|
||||
omitted the value 0 is implied. The following flags are available:
|
||||
|
||||
* `0`, `None`: No flags.
|
||||
|
||||
* `1<<0`, `NameOnly`: The request is for the name only, and not the
|
||||
CMI contents.
|
||||
|
||||
The `NameOnly` flag may be provded in a handshake response, and
|
||||
indicates that the server is interested in requests only for their
|
||||
implied dependency information. It may be provided on a request to
|
||||
indicate that only the CMI name is required, not its contents (for
|
||||
instance, when preprocessing). Note that a compiler may still make
|
||||
`NameOnly` requests even if the server did not ask for such.
|
||||
|
||||
#### Repository
|
||||
|
||||
All relative CMI file names are relative to a repository. (There are
|
||||
usually no absolute CMI files). The repository may be determined
|
||||
with:
|
||||
|
||||
`MODULE-REPO`
|
||||
|
||||
A PATHNAME response is expected. The `$pathname` may be an empty
|
||||
word, which is equivalent to `.`. When the response is a relative
|
||||
pathname, it must be relative to the client's current working
|
||||
directory (which might be a process on a different host to the
|
||||
server). You may set the repository to `/`, if you with to use paths
|
||||
relative to the root directory.
|
||||
|
||||
#### Exporting
|
||||
|
||||
A compilation of a module interface, partition or header unit can
|
||||
inform the builder with:
|
||||
|
||||
`MODULE-EXPORT $module [$flags]`
|
||||
|
||||
This will result in a PATHNAME response naming the Compiled Module
|
||||
Interface pathname to write.
|
||||
|
||||
The `MODULE-EXPORT` request does not indicate the module has been
|
||||
successfully compiled. At most one `MODULE-EXPORT` is to be made, and
|
||||
as the connection is for a single compilation, the builder may infer
|
||||
dependency relationships between the module being generated and import
|
||||
requests made.
|
||||
|
||||
Named module names and header unit names are distinguished by making
|
||||
the latter unambiguously look like file names. Firstly, they must be
|
||||
fully resolved according to the compiler's usual include path. If
|
||||
that results in an absolute name file name (beginning with `/`, or
|
||||
certain other OS-specific sequences), all is well. Otherwise a
|
||||
relative file name must be prefixed by `./` to be distinguished from a
|
||||
similarly named named module. This prefixing must occur, even if the
|
||||
header-unit's name contains characters that cannot appear in a named
|
||||
module's name.
|
||||
|
||||
It is expected that absolute header-unit names convert to relative CMI
|
||||
names, to keep all CMIs within the CMI repository. This means that
|
||||
steps must be taken to distinguish the CMIs for `/here` from `./here`,
|
||||
and this can be achieved by replacing the leading `./` directory with
|
||||
`,/`, which is visually similar but does not have the self-reference
|
||||
semantics of dot. Likewise, header-unit names containing `..`
|
||||
directories, can be remapped to `,,`. (When symlinks are involved
|
||||
`bob/dob/..` might not be `bob`, of course.) C++ header-unit
|
||||
semantics are such that there is no need to resolve multiple ways of
|
||||
spelling a particular header-unit to a unique CMI file.
|
||||
|
||||
Successful compilation of an interface is indicated with a subsequent:
|
||||
|
||||
`MODULE-COMPILED $module [$flags]`
|
||||
|
||||
request. This indicates the CMI file has been written to disk, so
|
||||
that any other compilations waiting on it may proceed. Depending on
|
||||
compiler implementation, the CMI may be written before the compilation
|
||||
completes. A single OK response is expected.
|
||||
|
||||
Compilation failure can be inferred by lack of a `MODULE-COMPILED`
|
||||
request. It is presumed the builder can determine this, as it is also
|
||||
responsible for launching and reaping the compiler invocations
|
||||
themselves.
|
||||
|
||||
#### Importing
|
||||
|
||||
Importation, including that of header-units, uses:
|
||||
|
||||
`MODULE-IMPORT $module [$flags]`
|
||||
|
||||
A PATHNAME response names the CMI file to be read. Should the builder
|
||||
have to invoke a compilation to produce the CMI, the response should
|
||||
be delayed until that occurs. If such a compilation fails, an error
|
||||
response should be provided to the requestor — which will then
|
||||
presumably fail in some manner.
|
||||
|
||||
#### Include Translation
|
||||
|
||||
Include translation can be determined with:
|
||||
|
||||
`INCLUDE-TRANSLATE $header [$flags]`
|
||||
|
||||
The header name, `$header`, is the fully resolved header name, in the
|
||||
above-mentioned unambiguous filename form. The response will either
|
||||
be a BOOL response indicating textual inclusion, or a PATHNAME
|
||||
response naming the CMI for such translation. The BOOL value is TRUE,
|
||||
if the header is known to be a textual header, and FALSE if nothing is
|
||||
known about it -- the latter might cause diagnostics about incomplete
|
||||
knowledge.
|
||||
|
||||
### GCC LTO Messages
|
||||
|
||||
These set of requests are used for GCC LTO jobserver integration with GNU Make
|
||||
|
||||
## Building libCody
|
||||
|
||||
Libcody is written in C++11. (It's a intended for compilers, so
|
||||
there'd be a bootstrapping problem if it used the latest and greatest.)
|
||||
|
||||
### Using configure and make.
|
||||
|
||||
It supports the usual `configure`, `make`, `make check` & `make install`
|
||||
sequence. It does not support building in the source directory --
|
||||
that just didn't drop out, and it's not how I build things (because,
|
||||
again, for compilers). Excitingly it uses my own `joust` test
|
||||
harness, so you'll need to build and install that somewhere, if you
|
||||
want the comfort of testing.
|
||||
|
||||
The following configure options are available, in addition to the usual set:
|
||||
|
||||
* `--enable-checking` Compile with assert-like checking. Defaults to on.
|
||||
|
||||
* `--with-tooldir=DIR` Prepend `DIR` to `PATH` when building (`DIR`
|
||||
need not already include the trailing `/bin`, and the right things
|
||||
happen). Use this if you need to point to non-standard tools that
|
||||
you usually don't have in your path. This path is also used when
|
||||
the configure script searches for programs.
|
||||
|
||||
* `--with-toolinc=DIR`, `--with-toollib=DIR`, include path and library
|
||||
path variants of `--with-tooldir`. If these are siblings of the
|
||||
tool bin directory, they'll be found automatically.
|
||||
|
||||
* `--with-compiler=NAME` Specify a particular compiler to use.
|
||||
Usually what configure finds is sufficiently usable.
|
||||
|
||||
* `--with-bugurl=URL` Override the bugreporting URL. Do this if
|
||||
you're providing libcody as part of a package that /you/ are
|
||||
supporting.
|
||||
|
||||
* `--enable-maintainer-mode` Specify that rules to rebuild things like
|
||||
`configure` (with `autoconf`) should be enabled. When not enabled,
|
||||
you'll get a message if these appear out of date, but that can
|
||||
happen naturally after an update or clone as `git`, in common with
|
||||
other VCs, doesn't preserve the relative ordering of file
|
||||
modifications. You can use `make MAINTAINER=touch` to shut make up,
|
||||
if this occurs (or manually execute the `autoconf` and related
|
||||
commands).
|
||||
|
||||
When building, you can override the default optimization flags with
|
||||
`CXXFLAGS=$flags`. I often build a debuggable library with `make
|
||||
CXXFLAGS=-g3`.
|
||||
|
||||
The `Makefile` will also parallelize according to the number of CPUs,
|
||||
unless you specify explicitly with a `-j` option. This is a little
|
||||
clunky, as it's not possible to figure out inside the makefile whether
|
||||
the user provided `-j`. (Or at least I've not figured out how.)
|
||||
|
||||
### Using cmake and make
|
||||
|
||||
#### In the clang/LLVM project
|
||||
|
||||
The primary motivation for a cmake implementation is to allow building
|
||||
libcody "in tree" in clang/LLVM. In that case, a checkout of libcody
|
||||
can be placed (or symbolically linked) into clang/tools. This will
|
||||
configure and build the library along with other LLVM dependencies.
|
||||
|
||||
*NOTE* This is not treated as an installable entity (it is present only
|
||||
for use by the project).
|
||||
|
||||
*NOTE* The testing targets would not be appropriate in this configuration;
|
||||
it is expected that lit-based testing of the required functionality will be
|
||||
done by the code using the library.
|
||||
|
||||
#### Stand-alone
|
||||
|
||||
For use on platforms that don't support configure & make effectively, it
|
||||
is possible to use the cmake & make process in stand-alone mode (similar
|
||||
to the configure & make process above).
|
||||
|
||||
An example use.
|
||||
```
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/path/to/installation -DCMAKE_CXX_COMPILER=clang++ /path/to/libcody/source
|
||||
make
|
||||
make install
|
||||
```
|
||||
Supported flags (additions to the usual cmake ones).
|
||||
|
||||
* `-DCODY_CHECKING=ON,OFF`: Compile with assert-like checking. (defaults ON)
|
||||
|
||||
* `-DCODY_WITHEXCEPTIONS=ON,OFF`: Compile with C++ exceptions and RTTI enabled.
|
||||
(defaults OFF, to be compatible with GCC and LLVM).
|
||||
|
||||
*TODO*: At present there is no support for `ctest` integration (this should be
|
||||
feasible, provided that `joust` is installed and can be discovered by `cmake`).
|
||||
|
||||
## API
|
||||
|
||||
The library defines entities in the `::Cody` namespace.
|
||||
|
||||
There are 4 user-visible classes:
|
||||
|
||||
* `Packet`: Responses to requests are `Packets`. These have a code,
|
||||
indicating the response kind, and a payload.
|
||||
|
||||
* `Client`: The compiler-end of a connection. Requests may be made
|
||||
and responses are returned.
|
||||
|
||||
* `Server`: The builder-end of a connection. Requests may be waited
|
||||
for, and responses made. Builders that serve multiple concurrent
|
||||
connections and spawn compilations to resolve dependencies may need
|
||||
to derive from this class to provide response queuing.
|
||||
|
||||
* `Resolver`: The processing engine of the builder side. User code is
|
||||
expected to derive from this class and provide virtual function
|
||||
overriders to affect the semantics of the resolver.
|
||||
|
||||
In addition there are a number of helpers to setup connections.
|
||||
|
||||
Logically the Client and the Server communicate via a sequential
|
||||
channel. The channel may be provided by:
|
||||
|
||||
* two pipes, with different file descriptors for reading and writing
|
||||
at each end.
|
||||
|
||||
* a socket, which will use the same file descriptor for reading and
|
||||
writing. the socket can be created in a number of ways, including
|
||||
Unix domain and IPv6 TCP, for which helpers are provided.
|
||||
|
||||
* a direct, in-process, connection, using buffer swapping.
|
||||
|
||||
The communication channel is presumed reliable.
|
||||
|
||||
Refer to the (currently very sparse) doxygen-generated documentation
|
||||
for details of the API.
|
||||
|
||||
## Examples
|
||||
|
||||
To create an in-process resolver, use the following boilerplate:
|
||||
|
||||
```
|
||||
class MyResolver : Cody::Resolver { ... stuff here ... };
|
||||
|
||||
Cody::Client *MakeClient (char const *maybe_ident)
|
||||
{
|
||||
auto *r = new MyResolver (...);
|
||||
auto *s = new Cody::Server (r);
|
||||
auto *c = new Cody::Client (s);
|
||||
|
||||
auto t = c->ConnectRequest ("ME", maybe_ident);
|
||||
if (t.GetCode () == Cody::Client::TC_CONNECT)
|
||||
;// Yay!
|
||||
else if (t.GetCode () == Cody::Client::TC_ERROR)
|
||||
report_error (t.GetString ());
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
For a remotely connecting client:
|
||||
```
|
||||
Cody::Client *MakeClient ()
|
||||
{
|
||||
char const *err = nullptr;
|
||||
int fd = OpenInet6 (char const **err, name, port);
|
||||
if (fd < 0)
|
||||
{ ... error... return nullptr;}
|
||||
|
||||
auto *c = new Cody::Client (fd);
|
||||
|
||||
auto t = c->ConnectRequest ("ME", maybe_ident);
|
||||
if (t.GetCode () == Cody::Client::TC_CONNECT)
|
||||
;// Yay!
|
||||
else if (t.GetCode () == Cody::Client::TC_ERROR)
|
||||
report_error (t.GetString ());
|
||||
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
# Future Directions
|
||||
|
||||
* Current Directory. There is no mechanism to check the builder and
|
||||
the compiler have the same working directory. Perhaps that should
|
||||
be addressed.
|
||||
|
||||
* Include path canonization and/or header file lookup. This can be
|
||||
expensive, particularly with many `-I` options, due to the system
|
||||
calls. Perhaps using a common resource would be cheaper?
|
||||
|
||||
* Generated header file lookup/construction. This is essentially the
|
||||
same problem as importing a module, and build systems are crap at
|
||||
dealing with this.
|
||||
|
||||
* Link-time compilations. Another place the compiler would like to
|
||||
ask the build system to do things.
|
||||
|
||||
* C++20 API entrypoints — std:string_view would be nice
|
||||
|
||||
* Exception-safety audit. Exceptions are not used, but memory
|
||||
exhaustion could happen. And perhaps user's resolver code employs
|
||||
exceptions?
|
||||
|
||||
<a name="1">1</a>: Or a small town in Wyoming
|
||||
|
||||
<a name="2">2</a>: This describes one common implementation technique.
|
||||
The std itself doesn't require such serializations, but the ability to
|
||||
create them is kind of the point. Also, 'compiler' is used where we
|
||||
mean any consumer of a module, and 'build system' where we mean any
|
||||
producer of a module.
|
||||
|
||||
<a name="3">3</a>: Even when the builder is managing a distributed set
|
||||
of compilations, the builder must have a mechanism to get source files
|
||||
to, and object files from, the compilations. That scheme can also
|
||||
transfer the CMI files.
|
387
libcody/buffer.cc
Normal file
387
libcody/buffer.cc
Normal file
@ -0,0 +1,387 @@
|
||||
// CODYlib -*- mode:c++ -*-
|
||||
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
// License: Apache v2.0
|
||||
|
||||
// Cody
|
||||
#include "internal.hh"
|
||||
// C++
|
||||
#include <algorithm>
|
||||
// C
|
||||
#include <cstring>
|
||||
// OS
|
||||
#include <unistd.h>
|
||||
#include <cerrno>
|
||||
|
||||
// MessageBuffer code
|
||||
|
||||
// Lines consist of words and end with a NEWLINE (0xa) char
|
||||
// Whitespace characters are TAB (0x9) and SPACE (0x20)
|
||||
// Words consist of non-whitespace chars separated by whitespace.
|
||||
// Multiple lines in one transaction are indicated by ending non-final
|
||||
// lines with a SEMICOLON (0x3b) word, immediately before the NEWLINE
|
||||
// Continuations with ; preceding it
|
||||
// Words matching regexp [-+_/%.a-zA-Z0-9]+ need no quoting.
|
||||
// Quoting with '...'
|
||||
// Anything outside of [-+_/%.a-zA-Z0-9] needs quoting
|
||||
// Anything outside of <= <space> or DEL or \' or \\ needs escaping.
|
||||
// Escapes are \\, \', \n, \t, \_, everything else as \<hex><hex>?
|
||||
// Spaces separate words, UTF8 encoding for non-ascii chars
|
||||
|
||||
namespace Cody {
|
||||
namespace Detail {
|
||||
|
||||
static const char CONTINUE = S2C(u8";");
|
||||
|
||||
void MessageBuffer::BeginLine ()
|
||||
{
|
||||
if (!buffer.empty ())
|
||||
{
|
||||
// Terminate the previous line with a continuation
|
||||
buffer.reserve (buffer.size () + 3);
|
||||
buffer.push_back (S2C(u8" "));
|
||||
buffer.push_back (CONTINUE);
|
||||
buffer.push_back (S2C(u8"\n"));
|
||||
}
|
||||
lastBol = buffer.size ();
|
||||
}
|
||||
|
||||
// QUOTE means 'maybe quote', we search it for quote-needing chars
|
||||
|
||||
void MessageBuffer::Append (char const *str, bool quote, size_t len)
|
||||
{
|
||||
if (len == ~size_t (0))
|
||||
len = strlen (str);
|
||||
|
||||
if (!len && !quote)
|
||||
return;
|
||||
|
||||
// We want to quote characters outside of [-+_A-Za-z0-9/%.], anything
|
||||
// that could remotely be shell-active. UTF8 encoding for non-ascii.
|
||||
if (quote && len)
|
||||
{
|
||||
quote = false;
|
||||
// Scan looking for quote-needing characters. We could just
|
||||
// append until we find one, but that's probably confusing
|
||||
for (size_t ix = len; ix--;)
|
||||
{
|
||||
unsigned char c = (unsigned char)str[ix];
|
||||
if (!((c >= S2C(u8"a") && c <= S2C(u8"z"))
|
||||
|| (c >= S2C(u8"A") && c <= S2C(u8"Z"))
|
||||
|| (c >= S2C(u8"0") && c <= S2C(u8"9"))
|
||||
|| c == S2C(u8"-") || c == S2C(u8"+") || c == S2C(u8"_")
|
||||
|| c == S2C(u8"/") || c == S2C(u8"%") || c == S2C(u8".")))
|
||||
{
|
||||
quote = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Maximal length of appended string
|
||||
buffer.reserve (buffer.size () + len * (quote ? 3 : 1) + 2);
|
||||
|
||||
if (quote)
|
||||
buffer.push_back (S2C(u8"'"));
|
||||
|
||||
for (auto *end = str + len; str != end;)
|
||||
{
|
||||
auto *e = end;
|
||||
|
||||
if (quote)
|
||||
// Look for next escape-needing char. More relaxed than
|
||||
// the earlier needs-quoting check.
|
||||
for (e = str; e != end; ++e)
|
||||
{
|
||||
unsigned char c = (unsigned char)*e;
|
||||
if (c < S2C(u8" ") || c == 0x7f
|
||||
|| c == S2C(u8"\\") || c == S2C(u8"'"))
|
||||
break;
|
||||
}
|
||||
buffer.insert (buffer.end (), str, e);
|
||||
str = e;
|
||||
|
||||
if (str == end)
|
||||
break;
|
||||
|
||||
buffer.push_back (S2C(u8"\\"));
|
||||
switch (unsigned char c = (unsigned char)*str++)
|
||||
{
|
||||
case S2C(u8"\t"):
|
||||
c = S2C(u8"t");
|
||||
goto append;
|
||||
|
||||
case S2C(u8"\n"):
|
||||
c = S2C(u8"n");
|
||||
goto append;
|
||||
|
||||
case S2C(u8"'"):
|
||||
case S2C(u8"\\"):
|
||||
append:
|
||||
buffer.push_back (c);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Full-on escape. Use 2 lower-case hex chars
|
||||
for (unsigned shift = 8; shift;)
|
||||
{
|
||||
shift -= 4;
|
||||
|
||||
char nibble = (c >> shift) & 0xf;
|
||||
nibble += S2C(u8"0");
|
||||
if (nibble > S2C(u8"9"))
|
||||
nibble += S2C(u8"a") - (S2C(u8"9") + 1);
|
||||
buffer.push_back (nibble);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (quote)
|
||||
buffer.push_back (S2C(u8"'"));
|
||||
}
|
||||
|
||||
void MessageBuffer::Append (char c)
|
||||
{
|
||||
buffer.push_back (c);
|
||||
}
|
||||
|
||||
void MessageBuffer::AppendInteger (unsigned u)
|
||||
{
|
||||
std::string v (std::to_string (u));
|
||||
AppendWord (v);
|
||||
}
|
||||
|
||||
int MessageBuffer::Write (int fd) noexcept
|
||||
{
|
||||
size_t limit = buffer.size () - lastBol;
|
||||
ssize_t count = write (fd, &buffer.data ()[lastBol], limit);
|
||||
|
||||
int err = 0;
|
||||
if (count < 0)
|
||||
err = errno;
|
||||
else
|
||||
{
|
||||
lastBol += count;
|
||||
if (size_t (count) != limit)
|
||||
err = EAGAIN;
|
||||
}
|
||||
|
||||
if (err != EAGAIN && err != EINTR)
|
||||
{
|
||||
// Reset for next message
|
||||
buffer.clear ();
|
||||
lastBol = 0;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int MessageBuffer::Read (int fd) noexcept
|
||||
{
|
||||
constexpr size_t blockSize = 200;
|
||||
|
||||
size_t lwm = buffer.size ();
|
||||
size_t hwm = buffer.capacity ();
|
||||
if (hwm - lwm < blockSize / 2)
|
||||
hwm += blockSize;
|
||||
buffer.resize (hwm);
|
||||
|
||||
auto iter = buffer.begin () + lwm;
|
||||
ssize_t count = read (fd, &*iter, hwm - lwm);
|
||||
buffer.resize (lwm + (count >= 0 ? count : 0));
|
||||
|
||||
if (count < 0)
|
||||
return errno;
|
||||
|
||||
if (!count)
|
||||
// End of file
|
||||
return -1;
|
||||
|
||||
bool more = true;
|
||||
for (;;)
|
||||
{
|
||||
auto newline = std::find (iter, buffer.end (), S2C(u8"\n"));
|
||||
if (newline == buffer.end ())
|
||||
break;
|
||||
more = newline != buffer.begin () && newline[-1] == CONTINUE;
|
||||
iter = newline + 1;
|
||||
|
||||
if (iter == buffer.end ())
|
||||
break;
|
||||
|
||||
if (!more)
|
||||
{
|
||||
// There is no continuation, but there are chars after the
|
||||
// newline. Truncate the buffer and return an error
|
||||
buffer.resize (iter - buffer.begin ());
|
||||
return EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return more ? EAGAIN : 0;
|
||||
}
|
||||
|
||||
int MessageBuffer::Lex (std::vector<std::string> &result)
|
||||
{
|
||||
result.clear ();
|
||||
|
||||
int err = ENOENT;
|
||||
if (IsAtEnd ())
|
||||
return ENOENT;
|
||||
|
||||
Assert (buffer.back () == S2C(u8"\n"));
|
||||
|
||||
auto iter = buffer.begin () + lastBol;
|
||||
|
||||
for (std::string *word = nullptr;;)
|
||||
{
|
||||
char c = *iter;
|
||||
|
||||
++iter;
|
||||
if (c == S2C(u8" ") || c == S2C(u8"\t"))
|
||||
{
|
||||
word = nullptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == S2C(u8"\n"))
|
||||
break;
|
||||
|
||||
if (c == CONTINUE)
|
||||
{
|
||||
// Line continuation
|
||||
if (word || *iter != S2C(u8"\n"))
|
||||
goto malformed;
|
||||
++iter;
|
||||
break;
|
||||
}
|
||||
|
||||
if (c <= S2C(u8" ") || c >= 0x7f)
|
||||
goto malformed;
|
||||
|
||||
if (!word)
|
||||
{
|
||||
result.emplace_back ();
|
||||
word = &result.back ();
|
||||
}
|
||||
|
||||
if (c == S2C(u8"'"))
|
||||
{
|
||||
// Quoted word
|
||||
for (;;)
|
||||
{
|
||||
c = *iter;
|
||||
|
||||
if (c == S2C(u8"\n"))
|
||||
{
|
||||
malformed:;
|
||||
result.clear ();
|
||||
iter = std::find (iter, buffer.end (), S2C(u8"\n"));
|
||||
auto back = iter;
|
||||
if (back[-1] == CONTINUE && back[-2] == S2C(u8" "))
|
||||
// Smells like a line continuation
|
||||
back -= 2;
|
||||
result.emplace_back (&buffer[lastBol],
|
||||
back - buffer.begin () - lastBol);
|
||||
++iter;
|
||||
lastBol = iter - buffer.begin ();
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
if (c < S2C(u8" ") || c >= 0x7f)
|
||||
goto malformed;
|
||||
|
||||
++iter;
|
||||
if (c == S2C(u8"'"))
|
||||
break;
|
||||
|
||||
if (c == S2C(u8"\\"))
|
||||
// escape
|
||||
switch (c = *iter)
|
||||
{
|
||||
case S2C(u8"\\"):
|
||||
case S2C(u8"'"):
|
||||
++iter;
|
||||
break;
|
||||
|
||||
case S2C(u8"n"):
|
||||
c = S2C(u8"\n");
|
||||
++iter;
|
||||
break;
|
||||
|
||||
case S2C(u8"_"):
|
||||
// We used to escape SPACE as \_, so accept that
|
||||
c = S2C(u8" ");
|
||||
++iter;
|
||||
break;
|
||||
|
||||
case S2C(u8"t"):
|
||||
c = S2C(u8"\t");
|
||||
++iter;
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
unsigned v = 0;
|
||||
for (unsigned nibble = 0; nibble != 2; nibble++)
|
||||
{
|
||||
c = *iter;
|
||||
if (c < S2C(u8"0"))
|
||||
{
|
||||
if (!nibble)
|
||||
goto malformed;
|
||||
break;
|
||||
}
|
||||
else if (c <= S2C(u8"9"))
|
||||
c -= S2C(u8"0");
|
||||
else if (c < S2C(u8"a"))
|
||||
{
|
||||
if (!nibble)
|
||||
goto malformed;
|
||||
break;
|
||||
}
|
||||
else if (c <= S2C(u8"f"))
|
||||
c -= S2C(u8"a") - 10;
|
||||
else
|
||||
{
|
||||
if (!nibble)
|
||||
goto malformed;
|
||||
break;
|
||||
}
|
||||
++iter;
|
||||
v = (v << 4) | c;
|
||||
}
|
||||
c = v;
|
||||
}
|
||||
}
|
||||
word->push_back (c);
|
||||
}
|
||||
}
|
||||
else
|
||||
// Unquoted character
|
||||
word->push_back (c);
|
||||
}
|
||||
lastBol = iter - buffer.begin ();
|
||||
if (result.empty ())
|
||||
return ENOENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MessageBuffer::LexedLine (std::string &str)
|
||||
{
|
||||
if (lastBol)
|
||||
{
|
||||
size_t pos = lastBol - 1;
|
||||
for (; pos; pos--)
|
||||
if (buffer[pos-1] == S2C(u8"\n"))
|
||||
break;
|
||||
|
||||
size_t end = lastBol - 1;
|
||||
if (buffer[end-1] == CONTINUE && buffer[end-2] == S2C(u8" "))
|
||||
// Strip line continuation
|
||||
end -= 2;
|
||||
str.append (&buffer[pos], end - pos);
|
||||
}
|
||||
}
|
||||
} // Detail
|
||||
} // Cody
|
1476
libcody/build-aux/config.guess
vendored
Executable file
1476
libcody/build-aux/config.guess
vendored
Executable file
File diff suppressed because it is too large
Load Diff
1833
libcody/build-aux/config.sub
vendored
Executable file
1833
libcody/build-aux/config.sub
vendored
Executable file
File diff suppressed because it is too large
Load Diff
518
libcody/build-aux/install-sh
Executable file
518
libcody/build-aux/install-sh
Executable file
@ -0,0 +1,518 @@
|
||||
#!/bin/sh
|
||||
# install - install a program, script, or datafile
|
||||
|
||||
scriptversion=2018-03-11.20; # UTC
|
||||
|
||||
# This originates from X11R5 (mit/util/scripts/install.sh), which was
|
||||
# later released in X11R6 (xc/config/util/install.sh) with the
|
||||
# following copyright and license.
|
||||
#
|
||||
# Copyright (C) 1994 X Consortium
|
||||
#
|
||||
# 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
|
||||
# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
|
||||
# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Except as contained in this notice, the name of the X Consortium shall not
|
||||
# be used in advertising or otherwise to promote the sale, use or other deal-
|
||||
# ings in this Software without prior written authorization from the X Consor-
|
||||
# tium.
|
||||
#
|
||||
#
|
||||
# FSF changes to this file are in the public domain.
|
||||
#
|
||||
# Calling this script install-sh is preferred over install.sh, to prevent
|
||||
# 'make' implicit rules from creating a file called install from it
|
||||
# when there is no Makefile.
|
||||
#
|
||||
# This script is compatible with the BSD install script, but was written
|
||||
# from scratch.
|
||||
|
||||
tab=' '
|
||||
nl='
|
||||
'
|
||||
IFS=" $tab$nl"
|
||||
|
||||
# Set DOITPROG to "echo" to test this script.
|
||||
|
||||
doit=${DOITPROG-}
|
||||
doit_exec=${doit:-exec}
|
||||
|
||||
# Put in absolute file names if you don't have them in your path;
|
||||
# or use environment vars.
|
||||
|
||||
chgrpprog=${CHGRPPROG-chgrp}
|
||||
chmodprog=${CHMODPROG-chmod}
|
||||
chownprog=${CHOWNPROG-chown}
|
||||
cmpprog=${CMPPROG-cmp}
|
||||
cpprog=${CPPROG-cp}
|
||||
mkdirprog=${MKDIRPROG-mkdir}
|
||||
mvprog=${MVPROG-mv}
|
||||
rmprog=${RMPROG-rm}
|
||||
stripprog=${STRIPPROG-strip}
|
||||
|
||||
posix_mkdir=
|
||||
|
||||
# Desired mode of installed file.
|
||||
mode=0755
|
||||
|
||||
chgrpcmd=
|
||||
chmodcmd=$chmodprog
|
||||
chowncmd=
|
||||
mvcmd=$mvprog
|
||||
rmcmd="$rmprog -f"
|
||||
stripcmd=
|
||||
|
||||
src=
|
||||
dst=
|
||||
dir_arg=
|
||||
dst_arg=
|
||||
|
||||
copy_on_change=false
|
||||
is_target_a_directory=possibly
|
||||
|
||||
usage="\
|
||||
Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
|
||||
or: $0 [OPTION]... SRCFILES... DIRECTORY
|
||||
or: $0 [OPTION]... -t DIRECTORY SRCFILES...
|
||||
or: $0 [OPTION]... -d DIRECTORIES...
|
||||
|
||||
In the 1st form, copy SRCFILE to DSTFILE.
|
||||
In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
|
||||
In the 4th, create DIRECTORIES.
|
||||
|
||||
Options:
|
||||
--help display this help and exit.
|
||||
--version display version info and exit.
|
||||
|
||||
-c (ignored)
|
||||
-C install only if different (preserve the last data modification time)
|
||||
-d create directories instead of installing files.
|
||||
-g GROUP $chgrpprog installed files to GROUP.
|
||||
-m MODE $chmodprog installed files to MODE.
|
||||
-o USER $chownprog installed files to USER.
|
||||
-s $stripprog installed files.
|
||||
-t DIRECTORY install into DIRECTORY.
|
||||
-T report an error if DSTFILE is a directory.
|
||||
|
||||
Environment variables override the default commands:
|
||||
CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
|
||||
RMPROG STRIPPROG
|
||||
"
|
||||
|
||||
while test $# -ne 0; do
|
||||
case $1 in
|
||||
-c) ;;
|
||||
|
||||
-C) copy_on_change=true;;
|
||||
|
||||
-d) dir_arg=true;;
|
||||
|
||||
-g) chgrpcmd="$chgrpprog $2"
|
||||
shift;;
|
||||
|
||||
--help) echo "$usage"; exit $?;;
|
||||
|
||||
-m) mode=$2
|
||||
case $mode in
|
||||
*' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*)
|
||||
echo "$0: invalid mode: $mode" >&2
|
||||
exit 1;;
|
||||
esac
|
||||
shift;;
|
||||
|
||||
-o) chowncmd="$chownprog $2"
|
||||
shift;;
|
||||
|
||||
-s) stripcmd=$stripprog;;
|
||||
|
||||
-t)
|
||||
is_target_a_directory=always
|
||||
dst_arg=$2
|
||||
# Protect names problematic for 'test' and other utilities.
|
||||
case $dst_arg in
|
||||
-* | [=\(\)!]) dst_arg=./$dst_arg;;
|
||||
esac
|
||||
shift;;
|
||||
|
||||
-T) is_target_a_directory=never;;
|
||||
|
||||
--version) echo "$0 $scriptversion"; exit $?;;
|
||||
|
||||
--) shift
|
||||
break;;
|
||||
|
||||
-*) echo "$0: invalid option: $1" >&2
|
||||
exit 1;;
|
||||
|
||||
*) break;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# We allow the use of options -d and -T together, by making -d
|
||||
# take the precedence; this is for compatibility with GNU install.
|
||||
|
||||
if test -n "$dir_arg"; then
|
||||
if test -n "$dst_arg"; then
|
||||
echo "$0: target directory not allowed when installing a directory." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
|
||||
# When -d is used, all remaining arguments are directories to create.
|
||||
# When -t is used, the destination is already specified.
|
||||
# Otherwise, the last argument is the destination. Remove it from $@.
|
||||
for arg
|
||||
do
|
||||
if test -n "$dst_arg"; then
|
||||
# $@ is not empty: it contains at least $arg.
|
||||
set fnord "$@" "$dst_arg"
|
||||
shift # fnord
|
||||
fi
|
||||
shift # arg
|
||||
dst_arg=$arg
|
||||
# Protect names problematic for 'test' and other utilities.
|
||||
case $dst_arg in
|
||||
-* | [=\(\)!]) dst_arg=./$dst_arg;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
if test $# -eq 0; then
|
||||
if test -z "$dir_arg"; then
|
||||
echo "$0: no input file specified." >&2
|
||||
exit 1
|
||||
fi
|
||||
# It's OK to call 'install-sh -d' without argument.
|
||||
# This can happen when creating conditional directories.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if test -z "$dir_arg"; then
|
||||
if test $# -gt 1 || test "$is_target_a_directory" = always; then
|
||||
if test ! -d "$dst_arg"; then
|
||||
echo "$0: $dst_arg: Is not a directory." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if test -z "$dir_arg"; then
|
||||
do_exit='(exit $ret); exit $ret'
|
||||
trap "ret=129; $do_exit" 1
|
||||
trap "ret=130; $do_exit" 2
|
||||
trap "ret=141; $do_exit" 13
|
||||
trap "ret=143; $do_exit" 15
|
||||
|
||||
# Set umask so as not to create temps with too-generous modes.
|
||||
# However, 'strip' requires both read and write access to temps.
|
||||
case $mode in
|
||||
# Optimize common cases.
|
||||
*644) cp_umask=133;;
|
||||
*755) cp_umask=22;;
|
||||
|
||||
*[0-7])
|
||||
if test -z "$stripcmd"; then
|
||||
u_plus_rw=
|
||||
else
|
||||
u_plus_rw='% 200'
|
||||
fi
|
||||
cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
|
||||
*)
|
||||
if test -z "$stripcmd"; then
|
||||
u_plus_rw=
|
||||
else
|
||||
u_plus_rw=,u+rw
|
||||
fi
|
||||
cp_umask=$mode$u_plus_rw;;
|
||||
esac
|
||||
fi
|
||||
|
||||
for src
|
||||
do
|
||||
# Protect names problematic for 'test' and other utilities.
|
||||
case $src in
|
||||
-* | [=\(\)!]) src=./$src;;
|
||||
esac
|
||||
|
||||
if test -n "$dir_arg"; then
|
||||
dst=$src
|
||||
dstdir=$dst
|
||||
test -d "$dstdir"
|
||||
dstdir_status=$?
|
||||
else
|
||||
|
||||
# Waiting for this to be detected by the "$cpprog $src $dsttmp" command
|
||||
# might cause directories to be created, which would be especially bad
|
||||
# if $src (and thus $dsttmp) contains '*'.
|
||||
if test ! -f "$src" && test ! -d "$src"; then
|
||||
echo "$0: $src does not exist." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if test -z "$dst_arg"; then
|
||||
echo "$0: no destination specified." >&2
|
||||
exit 1
|
||||
fi
|
||||
dst=$dst_arg
|
||||
|
||||
# If destination is a directory, append the input filename.
|
||||
if test -d "$dst"; then
|
||||
if test "$is_target_a_directory" = never; then
|
||||
echo "$0: $dst_arg: Is a directory" >&2
|
||||
exit 1
|
||||
fi
|
||||
dstdir=$dst
|
||||
dstbase=`basename "$src"`
|
||||
case $dst in
|
||||
*/) dst=$dst$dstbase;;
|
||||
*) dst=$dst/$dstbase;;
|
||||
esac
|
||||
dstdir_status=0
|
||||
else
|
||||
dstdir=`dirname "$dst"`
|
||||
test -d "$dstdir"
|
||||
dstdir_status=$?
|
||||
fi
|
||||
fi
|
||||
|
||||
case $dstdir in
|
||||
*/) dstdirslash=$dstdir;;
|
||||
*) dstdirslash=$dstdir/;;
|
||||
esac
|
||||
|
||||
obsolete_mkdir_used=false
|
||||
|
||||
if test $dstdir_status != 0; then
|
||||
case $posix_mkdir in
|
||||
'')
|
||||
# Create intermediate dirs using mode 755 as modified by the umask.
|
||||
# This is like FreeBSD 'install' as of 1997-10-28.
|
||||
umask=`umask`
|
||||
case $stripcmd.$umask in
|
||||
# Optimize common cases.
|
||||
*[2367][2367]) mkdir_umask=$umask;;
|
||||
.*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
|
||||
|
||||
*[0-7])
|
||||
mkdir_umask=`expr $umask + 22 \
|
||||
- $umask % 100 % 40 + $umask % 20 \
|
||||
- $umask % 10 % 4 + $umask % 2
|
||||
`;;
|
||||
*) mkdir_umask=$umask,go-w;;
|
||||
esac
|
||||
|
||||
# With -d, create the new directory with the user-specified mode.
|
||||
# Otherwise, rely on $mkdir_umask.
|
||||
if test -n "$dir_arg"; then
|
||||
mkdir_mode=-m$mode
|
||||
else
|
||||
mkdir_mode=
|
||||
fi
|
||||
|
||||
posix_mkdir=false
|
||||
case $umask in
|
||||
*[123567][0-7][0-7])
|
||||
# POSIX mkdir -p sets u+wx bits regardless of umask, which
|
||||
# is incompatible with FreeBSD 'install' when (umask & 300) != 0.
|
||||
;;
|
||||
*)
|
||||
# Note that $RANDOM variable is not portable (e.g. dash); Use it
|
||||
# here however when possible just to lower collision chance.
|
||||
tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
|
||||
|
||||
trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0
|
||||
|
||||
# Because "mkdir -p" follows existing symlinks and we likely work
|
||||
# directly in world-writeable /tmp, make sure that the '$tmpdir'
|
||||
# directory is successfully created first before we actually test
|
||||
# 'mkdir -p' feature.
|
||||
if (umask $mkdir_umask &&
|
||||
$mkdirprog $mkdir_mode "$tmpdir" &&
|
||||
exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1
|
||||
then
|
||||
if test -z "$dir_arg" || {
|
||||
# Check for POSIX incompatibilities with -m.
|
||||
# HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
|
||||
# other-writable bit of parent directory when it shouldn't.
|
||||
# FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
|
||||
test_tmpdir="$tmpdir/a"
|
||||
ls_ld_tmpdir=`ls -ld "$test_tmpdir"`
|
||||
case $ls_ld_tmpdir in
|
||||
d????-?r-*) different_mode=700;;
|
||||
d????-?--*) different_mode=755;;
|
||||
*) false;;
|
||||
esac &&
|
||||
$mkdirprog -m$different_mode -p -- "$test_tmpdir" && {
|
||||
ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"`
|
||||
test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
|
||||
}
|
||||
}
|
||||
then posix_mkdir=:
|
||||
fi
|
||||
rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir"
|
||||
else
|
||||
# Remove any dirs left behind by ancient mkdir implementations.
|
||||
rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null
|
||||
fi
|
||||
trap '' 0;;
|
||||
esac;;
|
||||
esac
|
||||
|
||||
if
|
||||
$posix_mkdir && (
|
||||
umask $mkdir_umask &&
|
||||
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
|
||||
)
|
||||
then :
|
||||
else
|
||||
|
||||
# The umask is ridiculous, or mkdir does not conform to POSIX,
|
||||
# or it failed possibly due to a race condition. Create the
|
||||
# directory the slow way, step by step, checking for races as we go.
|
||||
|
||||
case $dstdir in
|
||||
/*) prefix='/';;
|
||||
[-=\(\)!]*) prefix='./';;
|
||||
*) prefix='';;
|
||||
esac
|
||||
|
||||
oIFS=$IFS
|
||||
IFS=/
|
||||
set -f
|
||||
set fnord $dstdir
|
||||
shift
|
||||
set +f
|
||||
IFS=$oIFS
|
||||
|
||||
prefixes=
|
||||
|
||||
for d
|
||||
do
|
||||
test X"$d" = X && continue
|
||||
|
||||
prefix=$prefix$d
|
||||
if test -d "$prefix"; then
|
||||
prefixes=
|
||||
else
|
||||
if $posix_mkdir; then
|
||||
(umask=$mkdir_umask &&
|
||||
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
|
||||
# Don't fail if two instances are running concurrently.
|
||||
test -d "$prefix" || exit 1
|
||||
else
|
||||
case $prefix in
|
||||
*\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
|
||||
*) qprefix=$prefix;;
|
||||
esac
|
||||
prefixes="$prefixes '$qprefix'"
|
||||
fi
|
||||
fi
|
||||
prefix=$prefix/
|
||||
done
|
||||
|
||||
if test -n "$prefixes"; then
|
||||
# Don't fail if two instances are running concurrently.
|
||||
(umask $mkdir_umask &&
|
||||
eval "\$doit_exec \$mkdirprog $prefixes") ||
|
||||
test -d "$dstdir" || exit 1
|
||||
obsolete_mkdir_used=true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if test -n "$dir_arg"; then
|
||||
{ test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
|
||||
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
|
||||
{ test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
|
||||
test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
|
||||
else
|
||||
|
||||
# Make a couple of temp file names in the proper directory.
|
||||
dsttmp=${dstdirslash}_inst.$$_
|
||||
rmtmp=${dstdirslash}_rm.$$_
|
||||
|
||||
# Trap to clean up those temp files at exit.
|
||||
trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
|
||||
|
||||
# Copy the file name to the temp name.
|
||||
(umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
|
||||
|
||||
# and set any options; do chmod last to preserve setuid bits.
|
||||
#
|
||||
# If any of these fail, we abort the whole thing. If we want to
|
||||
# ignore errors from any of these, just make sure not to ignore
|
||||
# errors from the above "$doit $cpprog $src $dsttmp" command.
|
||||
#
|
||||
{ test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
|
||||
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
|
||||
{ test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
|
||||
{ test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
|
||||
|
||||
# If -C, don't bother to copy if it wouldn't change the file.
|
||||
if $copy_on_change &&
|
||||
old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
|
||||
new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
|
||||
set -f &&
|
||||
set X $old && old=:$2:$4:$5:$6 &&
|
||||
set X $new && new=:$2:$4:$5:$6 &&
|
||||
set +f &&
|
||||
test "$old" = "$new" &&
|
||||
$cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
|
||||
then
|
||||
rm -f "$dsttmp"
|
||||
else
|
||||
# Rename the file to the real destination.
|
||||
$doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
|
||||
|
||||
# The rename failed, perhaps because mv can't rename something else
|
||||
# to itself, or perhaps because mv is so ancient that it does not
|
||||
# support -f.
|
||||
{
|
||||
# Now remove or move aside any old file at destination location.
|
||||
# We try this two ways since rm can't unlink itself on some
|
||||
# systems and the destination file might be busy for other
|
||||
# reasons. In this case, the final cleanup might fail but the new
|
||||
# file should still install successfully.
|
||||
{
|
||||
test ! -f "$dst" ||
|
||||
$doit $rmcmd -f "$dst" 2>/dev/null ||
|
||||
{ $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
|
||||
{ $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
|
||||
} ||
|
||||
{ echo "$0: cannot unlink or rename $dst" >&2
|
||||
(exit 1); exit 1
|
||||
}
|
||||
} &&
|
||||
|
||||
# Now rename the file to the real destination.
|
||||
$doit $mvcmd "$dsttmp" "$dst"
|
||||
}
|
||||
fi || exit 1
|
||||
|
||||
trap '' 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Local variables:
|
||||
# eval: (add-hook 'before-save-hook 'time-stamp)
|
||||
# time-stamp-start: "scriptversion="
|
||||
# time-stamp-format: "%:y-%02m-%02d.%02H"
|
||||
# time-stamp-time-zone: "UTC0"
|
||||
# time-stamp-end: "; # UTC"
|
||||
# End:
|
336
libcody/client.cc
Normal file
336
libcody/client.cc
Normal file
@ -0,0 +1,336 @@
|
||||
// CODYlib -*- mode:c++ -*-
|
||||
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
// License: Apache v2.0
|
||||
|
||||
// Cody
|
||||
#include "internal.hh"
|
||||
// C
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
// Client code
|
||||
|
||||
namespace Cody {
|
||||
|
||||
// These do not need to be members
|
||||
static Packet ConnectResponse (std::vector<std::string> &words);
|
||||
static Packet PathnameResponse (std::vector<std::string> &words);
|
||||
static Packet OKResponse (std::vector<std::string> &words);
|
||||
static Packet IncludeTranslateResponse (std::vector<std::string> &words);
|
||||
|
||||
// Must be consistently ordered with the RequestCode enum
|
||||
static Packet (*const responseTable[Detail::RC_HWM])
|
||||
(std::vector<std::string> &) =
|
||||
{
|
||||
&ConnectResponse,
|
||||
&PathnameResponse,
|
||||
&PathnameResponse,
|
||||
&PathnameResponse,
|
||||
&OKResponse,
|
||||
&IncludeTranslateResponse,
|
||||
};
|
||||
|
||||
Client::Client ()
|
||||
{
|
||||
fd.from = fd.to = -1;
|
||||
}
|
||||
|
||||
Client::Client (Client &&src)
|
||||
: write (std::move (src.write)),
|
||||
read (std::move (src.read)),
|
||||
corked (std::move (src.corked)),
|
||||
is_direct (src.is_direct),
|
||||
is_connected (src.is_connected)
|
||||
{
|
||||
if (is_direct)
|
||||
server = src.server;
|
||||
else
|
||||
{
|
||||
fd.from = src.fd.from;
|
||||
fd.to = src.fd.to;
|
||||
}
|
||||
}
|
||||
|
||||
Client::~Client ()
|
||||
{
|
||||
}
|
||||
|
||||
Client &Client::operator= (Client &&src)
|
||||
{
|
||||
write = std::move (src.write);
|
||||
read = std::move (src.read);
|
||||
corked = std::move (src.corked);
|
||||
is_direct = src.is_direct;
|
||||
is_connected = src.is_connected;
|
||||
if (is_direct)
|
||||
server = src.server;
|
||||
else
|
||||
{
|
||||
fd.from = src.fd.from;
|
||||
fd.to = src.fd.to;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
int Client::CommunicateWithServer ()
|
||||
{
|
||||
write.PrepareToWrite ();
|
||||
read.PrepareToRead ();
|
||||
if (IsDirect ())
|
||||
server->DirectProcess (write, read);
|
||||
else
|
||||
{
|
||||
// Write the write buffer
|
||||
while (int e = write.Write (fd.to))
|
||||
if (e != EAGAIN && e != EINTR)
|
||||
return e;
|
||||
// Read the read buffer
|
||||
while (int e = read.Read (fd.from))
|
||||
if (e != EAGAIN && e != EINTR)
|
||||
return e;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Packet CommunicationError (int err)
|
||||
{
|
||||
std::string e {u8"communication error:"};
|
||||
e.append (strerror (err));
|
||||
|
||||
return Packet (Client::PC_ERROR, std::move (e));
|
||||
}
|
||||
|
||||
Packet Client::ProcessResponse (std::vector<std::string> &words,
|
||||
unsigned code, bool isLast)
|
||||
{
|
||||
if (int e = read.Lex (words))
|
||||
{
|
||||
if (e == EINVAL)
|
||||
{
|
||||
std::string msg (u8"malformed string '");
|
||||
msg.append (words[0]);
|
||||
msg.append (u8"'");
|
||||
return Packet (Client::PC_ERROR, std::move (msg));
|
||||
}
|
||||
else
|
||||
return Packet (Client::PC_ERROR, u8"missing response");
|
||||
}
|
||||
|
||||
Assert (!words.empty ());
|
||||
if (words[0] == u8"ERROR")
|
||||
return Packet (Client::PC_ERROR,
|
||||
std::move (words.size () == 2 ? words[1]
|
||||
: u8"malformed error response"));
|
||||
|
||||
if (isLast && !read.IsAtEnd ())
|
||||
return Packet (Client::PC_ERROR,
|
||||
std::string (u8"unexpected extra response"));
|
||||
|
||||
Assert (code < Detail::RC_HWM);
|
||||
Packet result (responseTable[code] (words));
|
||||
result.SetRequest (code);
|
||||
if (result.GetCode () == Client::PC_ERROR && result.GetString ().empty ())
|
||||
{
|
||||
std::string msg {u8"malformed response '"};
|
||||
|
||||
read.LexedLine (msg);
|
||||
msg.append (u8"'");
|
||||
result.GetString () = std::move (msg);
|
||||
}
|
||||
else if (result.GetCode () == Client::PC_CONNECT)
|
||||
is_connected = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Packet Client::MaybeRequest (unsigned code)
|
||||
{
|
||||
if (IsCorked ())
|
||||
{
|
||||
corked.push_back (code);
|
||||
return Packet (PC_CORKED);
|
||||
}
|
||||
|
||||
if (int err = CommunicateWithServer ())
|
||||
return CommunicationError (err);
|
||||
|
||||
std::vector<std::string> words;
|
||||
return ProcessResponse(words, code, true);
|
||||
}
|
||||
|
||||
void Client::Cork ()
|
||||
{
|
||||
if (corked.empty ())
|
||||
corked.push_back (-1);
|
||||
}
|
||||
|
||||
std::vector<Packet> Client::Uncork ()
|
||||
{
|
||||
std::vector<Packet> result;
|
||||
|
||||
if (corked.size () > 1)
|
||||
{
|
||||
if (int err = CommunicateWithServer ())
|
||||
result.emplace_back (CommunicationError (err));
|
||||
else
|
||||
{
|
||||
std::vector<std::string> words;
|
||||
for (auto iter = corked.begin () + 1; iter != corked.end ();)
|
||||
{
|
||||
char code = *iter;
|
||||
++iter;
|
||||
result.emplace_back (ProcessResponse (words, code,
|
||||
iter == corked.end ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
corked.clear ();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Now the individual message handlers
|
||||
|
||||
// HELLO $vernum $agent $ident
|
||||
Packet Client::Connect (char const *agent, char const *ident,
|
||||
size_t alen, size_t ilen)
|
||||
{
|
||||
write.BeginLine ();
|
||||
write.AppendWord (u8"HELLO");
|
||||
write.AppendInteger (Version);
|
||||
write.AppendWord (agent, true, alen);
|
||||
write.AppendWord (ident, true, ilen);
|
||||
write.EndLine ();
|
||||
|
||||
return MaybeRequest (Detail::RC_CONNECT);
|
||||
}
|
||||
|
||||
// HELLO $version $agent [$flags]
|
||||
Packet ConnectResponse (std::vector<std::string> &words)
|
||||
{
|
||||
if (words[0] == u8"HELLO" && (words.size () == 3 || words.size () == 4))
|
||||
{
|
||||
char *eptr;
|
||||
unsigned long val = strtoul (words[1].c_str (), &eptr, 10);
|
||||
|
||||
unsigned version = unsigned (val);
|
||||
if (*eptr || version != val || version < Version)
|
||||
return Packet (Client::PC_ERROR, u8"incompatible version");
|
||||
else
|
||||
{
|
||||
unsigned flags = 0;
|
||||
if (words.size () == 4)
|
||||
{
|
||||
val = strtoul (words[3].c_str (), &eptr, 10);
|
||||
flags = unsigned (val);
|
||||
}
|
||||
return Packet (Client::PC_CONNECT, flags);
|
||||
}
|
||||
}
|
||||
|
||||
return Packet (Client::PC_ERROR, u8"");
|
||||
}
|
||||
|
||||
// MODULE-REPO
|
||||
Packet Client::ModuleRepo ()
|
||||
{
|
||||
write.BeginLine ();
|
||||
write.AppendWord (u8"MODULE-REPO");
|
||||
write.EndLine ();
|
||||
|
||||
return MaybeRequest (Detail::RC_MODULE_REPO);
|
||||
}
|
||||
|
||||
// PATHNAME $dir | ERROR
|
||||
Packet PathnameResponse (std::vector<std::string> &words)
|
||||
{
|
||||
if (words[0] == u8"PATHNAME" && words.size () == 2)
|
||||
return Packet (Client::PC_PATHNAME, std::move (words[1]));
|
||||
|
||||
return Packet (Client::PC_ERROR, u8"");
|
||||
}
|
||||
|
||||
// OK or ERROR
|
||||
Packet OKResponse (std::vector<std::string> &words)
|
||||
{
|
||||
if (words[0] == u8"OK")
|
||||
return Packet (Client::PC_OK);
|
||||
else
|
||||
return Packet (Client::PC_ERROR,
|
||||
words.size () == 2 ? std::move (words[1]) : "");
|
||||
}
|
||||
|
||||
// MODULE-EXPORT $modulename [$flags]
|
||||
Packet Client::ModuleExport (char const *module, Flags flags, size_t mlen)
|
||||
{
|
||||
write.BeginLine ();
|
||||
write.AppendWord (u8"MODULE-EXPORT");
|
||||
write.AppendWord (module, true, mlen);
|
||||
if (flags != Flags::None)
|
||||
write.AppendInteger (unsigned (flags));
|
||||
write.EndLine ();
|
||||
|
||||
return MaybeRequest (Detail::RC_MODULE_EXPORT);
|
||||
}
|
||||
|
||||
// MODULE-IMPORT $modulename [$flags]
|
||||
Packet Client::ModuleImport (char const *module, Flags flags, size_t mlen)
|
||||
{
|
||||
write.BeginLine ();
|
||||
write.AppendWord (u8"MODULE-IMPORT");
|
||||
write.AppendWord (module, true, mlen);
|
||||
if (flags != Flags::None)
|
||||
write.AppendInteger (unsigned (flags));
|
||||
write.EndLine ();
|
||||
|
||||
return MaybeRequest (Detail::RC_MODULE_IMPORT);
|
||||
}
|
||||
|
||||
// MODULE-COMPILED $modulename [$flags]
|
||||
Packet Client::ModuleCompiled (char const *module, Flags flags, size_t mlen)
|
||||
{
|
||||
write.BeginLine ();
|
||||
write.AppendWord (u8"MODULE-COMPILED");
|
||||
write.AppendWord (module, true, mlen);
|
||||
if (flags != Flags::None)
|
||||
write.AppendInteger (unsigned (flags));
|
||||
write.EndLine ();
|
||||
|
||||
return MaybeRequest (Detail::RC_MODULE_COMPILED);
|
||||
}
|
||||
|
||||
// INCLUDE-TRANSLATE $includename [$flags]
|
||||
Packet Client::IncludeTranslate (char const *include, Flags flags, size_t ilen)
|
||||
{
|
||||
write.BeginLine ();
|
||||
write.AppendWord (u8"INCLUDE-TRANSLATE");
|
||||
write.AppendWord (include, true, ilen);
|
||||
if (flags != Flags::None)
|
||||
write.AppendInteger (unsigned (flags));
|
||||
write.EndLine ();
|
||||
|
||||
return MaybeRequest (Detail::RC_INCLUDE_TRANSLATE);
|
||||
}
|
||||
|
||||
// BOOL $knowntextualness
|
||||
// PATHNAME $cmifile
|
||||
Packet IncludeTranslateResponse (std::vector<std::string> &words)
|
||||
{
|
||||
if (words[0] == u8"BOOL" && words.size () == 2)
|
||||
{
|
||||
if (words[1] == u8"FALSE")
|
||||
return Packet (Client::PC_BOOL, 0);
|
||||
else if (words[1] == u8"TRUE")
|
||||
return Packet (Client::PC_BOOL, 1);
|
||||
else
|
||||
return Packet (Client::PC_ERROR, u8"");
|
||||
}
|
||||
else
|
||||
return PathnameResponse (words);
|
||||
}
|
||||
|
||||
}
|
||||
|
43
libcody/cmake/libcody-config-ix.cmake
Normal file
43
libcody/cmake/libcody-config-ix.cmake
Normal file
@ -0,0 +1,43 @@
|
||||
# message(STATUS "*top config-ix* CMAKE_SYSTEM : ${CMAKE_SYSTEM}")
|
||||
|
||||
include(CheckIncludeFile)
|
||||
include(CheckIncludeFileCXX)
|
||||
#include(CheckLibraryExists)
|
||||
#include(CheckSymbolExists)
|
||||
include(CheckFunctionExists)
|
||||
#include(CheckCXXSourceCompiles)
|
||||
#include(TestBigEndian)
|
||||
include(CheckCCompilerFlag)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
# Flags
|
||||
|
||||
check_cxx_compiler_flag(-stdlib=libc++ LIBCODY_CXX_HAS_STDLIB_FLAG)
|
||||
|
||||
check_cxx_compiler_flag(-fno-enforce-eh-specs LIBCODY_HAS_NOENFORCE)
|
||||
check_cxx_compiler_flag(-fno-stack-protector LIBCODY_HAS_NOSTACKPROT)
|
||||
check_cxx_compiler_flag(-fno-threadsafe-statics LIBCODY_HAS_NOTHREADSAFESTATICS)
|
||||
|
||||
check_cxx_compiler_flag(-Wno-gnu-zero-variadic-macro-arguments LIBCODY_CXX_W_GZVMA)
|
||||
|
||||
# Address github issue #10
|
||||
if (NOT CODY_WITHEXCEPTIONS)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")
|
||||
if (LIBCODY_HAS_NOENFORCE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-enforce-eh-specs")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (LIBCODY_HAS_NOSTACKPROT)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector")
|
||||
endif()
|
||||
if (LIBCODY_HAS_NOTHREADSAFESTATICS)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-threadsafe-statics")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall -Woverloaded-virtual -Wshadow")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -Wno-unused-variable")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-field-initializers")
|
||||
if (LIBCODY_CXX_W_GZVMA)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-gnu-zero-variadic-macro-arguments")
|
||||
endif ()
|
800
libcody/cody.hh
Normal file
800
libcody/cody.hh
Normal file
@ -0,0 +1,800 @@
|
||||
// CODYlib -*- mode:c++ -*-
|
||||
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
// License: Apache v2.0
|
||||
|
||||
#ifndef CODY_HH
|
||||
#define CODY_HH 1
|
||||
|
||||
// Have a known-good list of networking systems
|
||||
#if defined (__unix__) || defined (__MACH__)
|
||||
#define CODY_NETWORKING 1
|
||||
#else
|
||||
#define CODY_NETWORKING 0
|
||||
#endif
|
||||
#if 0 // For testing
|
||||
#undef CODY_NETWORKING
|
||||
#define CODY_NETWORKING 0
|
||||
#endif
|
||||
|
||||
// C++
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
// C
|
||||
#include <cstddef>
|
||||
// OS
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#if CODY_NETWORKING
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
namespace Cody {
|
||||
|
||||
// Set version to 1, as this is completely incompatible with 0.
|
||||
// Fortunately both versions 0 and 1 will recognize each other's HELLO
|
||||
// messages sufficiently to error out
|
||||
constexpr unsigned Version = 1;
|
||||
|
||||
// FIXME: I guess we need a file-handle abstraction here
|
||||
// Is windows DWORDPTR still?, or should it be FILE *? (ew).
|
||||
|
||||
namespace Detail {
|
||||
|
||||
// C++11 doesn't have utf8 character literals :(
|
||||
|
||||
template<unsigned I>
|
||||
constexpr char S2C (char const (&s)[I])
|
||||
{
|
||||
static_assert (I == 2, "only single octet strings may be converted");
|
||||
return s[0];
|
||||
}
|
||||
|
||||
/// Internal buffering class. Used to concatenate outgoing messages
|
||||
/// and Lex incoming ones.
|
||||
class MessageBuffer
|
||||
{
|
||||
std::vector<char> buffer; ///< buffer holding the message
|
||||
size_t lastBol = 0; ///< location of the most recent Beginning Of
|
||||
///< Line, or position we've readed when writing
|
||||
|
||||
public:
|
||||
MessageBuffer () = default;
|
||||
~MessageBuffer () = default;
|
||||
MessageBuffer (MessageBuffer &&) = default;
|
||||
MessageBuffer &operator= (MessageBuffer &&) = default;
|
||||
|
||||
public:
|
||||
///
|
||||
/// Finalize a buffer to be written. No more lines can be added to
|
||||
/// the buffer. Use before a sequence of Write calls.
|
||||
void PrepareToWrite ()
|
||||
{
|
||||
buffer.push_back (u8"\n"[0]);
|
||||
lastBol = 0;
|
||||
}
|
||||
///
|
||||
/// Prepare a buffer for reading. Use before a sequence of Read calls.
|
||||
void PrepareToRead ()
|
||||
{
|
||||
buffer.clear ();
|
||||
lastBol = 0;
|
||||
}
|
||||
|
||||
public:
|
||||
/// Begin a message line. Use before a sequence of Append and
|
||||
/// related calls.
|
||||
void BeginLine ();
|
||||
/// End a message line. Use after a sequence of Append and related calls.
|
||||
void EndLine () {}
|
||||
|
||||
public:
|
||||
/// Append a string to the current line. No whitespace is prepended
|
||||
/// or appended.
|
||||
///
|
||||
/// @param str the string to be written
|
||||
/// @param maybe_quote indicate if there's a possibility the string
|
||||
/// contains characters that need quoting. Defaults to false.
|
||||
/// It is always safe to set
|
||||
/// this true, but that causes an additional scan of the string.
|
||||
/// @param len The length of the string. If not specified, strlen
|
||||
/// is used to find the length.
|
||||
void Append (char const *str, bool maybe_quote = false,
|
||||
size_t len = ~size_t (0));
|
||||
|
||||
///
|
||||
/// Add whitespace word separator. Multiple adjacent whitespace is fine.
|
||||
void Space ()
|
||||
{
|
||||
Append (Detail::S2C(u8" "));
|
||||
}
|
||||
|
||||
public:
|
||||
/// Add a word as with Append, but prefixing whitespace to make a
|
||||
/// separate word
|
||||
void AppendWord (char const *str, bool maybe_quote = false,
|
||||
size_t len = ~size_t (0))
|
||||
{
|
||||
if (buffer.size () != lastBol)
|
||||
Space ();
|
||||
Append (str, maybe_quote, len);
|
||||
}
|
||||
/// Add a word as with AppendWord
|
||||
/// @param str the string to append
|
||||
/// @param maybe_quote string might need quoting, as for Append
|
||||
void AppendWord (std::string const &str, bool maybe_quote = false)
|
||||
{
|
||||
AppendWord (str.data (), maybe_quote, str.size ());
|
||||
}
|
||||
///
|
||||
/// Add an integral value, prepending a space.
|
||||
void AppendInteger (unsigned u);
|
||||
|
||||
private:
|
||||
/// Append a literal character.
|
||||
/// @param c character to append
|
||||
void Append (char c);
|
||||
|
||||
public:
|
||||
/// Lex the next input line into a vector of words.
|
||||
/// @param words filled with a vector of lexed strings
|
||||
/// @result 0 if no errors, an errno value on lexxing error such as
|
||||
/// there being no next line (ENOENT), or malformed quoting (EINVAL)
|
||||
int Lex (std::vector<std::string> &words);
|
||||
|
||||
public:
|
||||
/// Append the most-recently lexxed line to a string. May be useful
|
||||
/// in error messages. The unparsed line is appended -- before any
|
||||
/// unquoting.
|
||||
/// If we had c++17 string_view, we'd simply return a view of the
|
||||
/// line, and leave it to the caller to do any concatenation.
|
||||
/// @param l string to-which the lexxed line is appended.
|
||||
void LexedLine (std::string &l);
|
||||
|
||||
public:
|
||||
/// Detect if we have reached the end of the input buffer.
|
||||
/// I.e. there are no more lines to Lex
|
||||
/// @result True if at end
|
||||
bool IsAtEnd () const
|
||||
{
|
||||
return lastBol == buffer.size ();
|
||||
}
|
||||
|
||||
public:
|
||||
/// Read from end point into a read buffer, as with read(2). This will
|
||||
/// not block , unless FD is blocking, and there is nothing
|
||||
/// immediately available.
|
||||
/// @param fd file descriptor to read from. This may be a regular
|
||||
/// file, pipe or socket.
|
||||
/// @result on error returns errno. If end of file occurs, returns
|
||||
/// -1. At end of message returns 0. If there is more needed
|
||||
/// returns EAGAIN (or possibly EINTR). If the message is
|
||||
/// malformed, returns EINVAL.
|
||||
int Read (int fd) noexcept;
|
||||
|
||||
public:
|
||||
/// Write to an end point from a write buffer, as with write(2). As
|
||||
/// with Read, this will not usually block.
|
||||
/// @param fd file descriptor to write to. This may be a regular
|
||||
/// file, pipe or socket.
|
||||
/// @result on error returns errno.
|
||||
/// At end of message returns 0. If there is more to write
|
||||
/// returns EAGAIN (or possibly EINTR).
|
||||
int Write (int fd) noexcept;
|
||||
};
|
||||
|
||||
///
|
||||
/// Request codes. Perhaps this should be exposed? These are likely
|
||||
/// useful to servers that queue requests.
|
||||
enum RequestCode
|
||||
{
|
||||
RC_CONNECT,
|
||||
RC_MODULE_REPO,
|
||||
RC_MODULE_EXPORT,
|
||||
RC_MODULE_IMPORT,
|
||||
RC_MODULE_COMPILED,
|
||||
RC_INCLUDE_TRANSLATE,
|
||||
RC_HWM
|
||||
};
|
||||
|
||||
/// Internal file descriptor tuple. It's used as an anonymous union member.
|
||||
struct FD
|
||||
{
|
||||
int from; ///< Read from this FD
|
||||
int to; ///< Write to this FD
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Flags for various requests
|
||||
enum class Flags : unsigned
|
||||
{
|
||||
None,
|
||||
NameOnly = 1<<0, // Only querying for CMI names, not contents
|
||||
};
|
||||
|
||||
inline Flags operator& (Flags a, Flags b)
|
||||
{
|
||||
return Flags (unsigned (a) & unsigned (b));
|
||||
}
|
||||
inline Flags operator| (Flags a, Flags b)
|
||||
{
|
||||
return Flags (unsigned (a) | unsigned (b));
|
||||
}
|
||||
|
||||
///
|
||||
/// Response data for a request. Returned by Client's request calls,
|
||||
/// which return a single Packet. When the connection is Corked, the
|
||||
/// Uncork call will return a vector of Packets.
|
||||
class Packet
|
||||
{
|
||||
public:
|
||||
///
|
||||
/// Packet is a variant structure. These are the possible content types.
|
||||
enum Category { INTEGER, STRING, VECTOR};
|
||||
|
||||
private:
|
||||
// std:variant is a C++17 thing, so we're doing this ourselves.
|
||||
union
|
||||
{
|
||||
size_t integer; ///< Integral value
|
||||
std::string string; ///< String value
|
||||
std::vector<std::string> vector; ///< Vector of string value
|
||||
};
|
||||
Category cat : 2; ///< Discriminator
|
||||
|
||||
private:
|
||||
unsigned short code = 0; ///< Packet type
|
||||
unsigned short request = 0;
|
||||
|
||||
public:
|
||||
Packet (unsigned c, size_t i = 0)
|
||||
: integer (i), cat (INTEGER), code (c)
|
||||
{
|
||||
}
|
||||
Packet (unsigned c, std::string &&s)
|
||||
: string (std::move (s)), cat (STRING), code (c)
|
||||
{
|
||||
}
|
||||
Packet (unsigned c, std::string const &s)
|
||||
: string (s), cat (STRING), code (c)
|
||||
{
|
||||
}
|
||||
Packet (unsigned c, std::vector<std::string> &&v)
|
||||
: vector (std::move (v)), cat (VECTOR), code (c)
|
||||
{
|
||||
}
|
||||
// No non-move constructor from a vector. You should not be doing
|
||||
// that.
|
||||
|
||||
// Only move constructor and move assignment
|
||||
Packet (Packet &&t)
|
||||
{
|
||||
Create (std::move (t));
|
||||
}
|
||||
Packet &operator= (Packet &&t)
|
||||
{
|
||||
Destroy ();
|
||||
Create (std::move (t));
|
||||
|
||||
return *this;
|
||||
}
|
||||
~Packet ()
|
||||
{
|
||||
Destroy ();
|
||||
}
|
||||
|
||||
private:
|
||||
///
|
||||
/// Variant move creation from another packet
|
||||
void Create (Packet &&t);
|
||||
///
|
||||
/// Variant destruction
|
||||
void Destroy ();
|
||||
|
||||
public:
|
||||
///
|
||||
/// Return the packet type
|
||||
unsigned GetCode () const
|
||||
{
|
||||
return code;
|
||||
}
|
||||
///
|
||||
/// Return the packet type
|
||||
unsigned GetRequest () const
|
||||
{
|
||||
return request;
|
||||
}
|
||||
void SetRequest (unsigned r)
|
||||
{
|
||||
request = r;
|
||||
}
|
||||
///
|
||||
/// Return the category of the packet's payload
|
||||
Category GetCategory () const
|
||||
{
|
||||
return cat;
|
||||
}
|
||||
|
||||
public:
|
||||
///
|
||||
/// Return an integral payload. Undefined if the category is not INTEGER
|
||||
size_t GetInteger () const
|
||||
{
|
||||
return integer;
|
||||
}
|
||||
///
|
||||
/// Return (a reference to) a string payload. Undefined if the
|
||||
/// category is not STRING
|
||||
std::string const &GetString () const
|
||||
{
|
||||
return string;
|
||||
}
|
||||
std::string &GetString ()
|
||||
{
|
||||
return string;
|
||||
}
|
||||
///
|
||||
/// Return (a reference to) a constant vector of strings payload.
|
||||
/// Undefined if the category is not VECTOR
|
||||
std::vector<std::string> const &GetVector () const
|
||||
{
|
||||
return vector;
|
||||
}
|
||||
///
|
||||
/// Return (a reference to) a non-conatant vector of strings payload.
|
||||
/// Undefined if the category is not VECTOR
|
||||
std::vector<std::string> &GetVector ()
|
||||
{
|
||||
return vector;
|
||||
}
|
||||
};
|
||||
|
||||
class Server;
|
||||
|
||||
///
|
||||
/// Client-side (compiler) object.
|
||||
class Client
|
||||
{
|
||||
public:
|
||||
/// Response packet codes
|
||||
enum PacketCode
|
||||
{
|
||||
PC_CORKED, ///< Messages are corked
|
||||
PC_CONNECT, ///< Packet is integer version
|
||||
PC_ERROR, ///< Packet is error string
|
||||
PC_OK,
|
||||
PC_BOOL,
|
||||
PC_PATHNAME
|
||||
};
|
||||
|
||||
private:
|
||||
Detail::MessageBuffer write; ///< Outgoing write buffer
|
||||
Detail::MessageBuffer read; ///< Incoming read buffer
|
||||
std::string corked; ///< Queued request tags
|
||||
union
|
||||
{
|
||||
Detail::FD fd; ///< FDs connecting to server
|
||||
Server *server; ///< Directly connected server
|
||||
};
|
||||
bool is_direct = false; ///< Discriminator
|
||||
bool is_connected = false; /// Connection handshake succesful
|
||||
|
||||
private:
|
||||
Client ();
|
||||
public:
|
||||
/// Direct connection constructor.
|
||||
/// @param s Server to directly connect
|
||||
Client (Server *s)
|
||||
: Client ()
|
||||
{
|
||||
is_direct = true;
|
||||
server = s;
|
||||
}
|
||||
/// Communication connection constructor
|
||||
/// @param from file descriptor to read from
|
||||
/// @param to file descriptor to write to, defaults to from
|
||||
Client (int from, int to = -1)
|
||||
: Client ()
|
||||
{
|
||||
fd.from = from;
|
||||
fd.to = to < 0 ? from : to;
|
||||
}
|
||||
~Client ();
|
||||
// We have to provide our own move variants, because of the variant member.
|
||||
Client (Client &&);
|
||||
Client &operator= (Client &&);
|
||||
|
||||
public:
|
||||
///
|
||||
/// Direct connection predicate
|
||||
bool IsDirect () const
|
||||
{
|
||||
return is_direct;
|
||||
}
|
||||
///
|
||||
/// Successful handshake predicate
|
||||
bool IsConnected () const
|
||||
{
|
||||
return is_connected;
|
||||
}
|
||||
|
||||
public:
|
||||
///
|
||||
/// Get the read FD
|
||||
/// @result the FD to read from, -1 if a direct connection
|
||||
int GetFDRead () const
|
||||
{
|
||||
return is_direct ? -1 : fd.from;
|
||||
}
|
||||
///
|
||||
/// Get the write FD
|
||||
/// @result the FD to write to, -1 if a direct connection
|
||||
int GetFDWrite () const
|
||||
{
|
||||
return is_direct ? -1 : fd.to;
|
||||
}
|
||||
///
|
||||
/// Get the directly-connected server
|
||||
/// @result the server, or nullptr if a communication connection
|
||||
Server *GetServer () const
|
||||
{
|
||||
return is_direct ? server : nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
///
|
||||
/// Perform connection handshake. All othe requests will result in
|
||||
/// errors, until handshake is succesful.
|
||||
/// @param agent compiler identification
|
||||
/// @param ident compilation identifiation (maybe nullptr)
|
||||
/// @param alen length of agent string, if known
|
||||
/// @param ilen length of ident string, if known
|
||||
/// @result packet indicating success (or deferrment) of the
|
||||
/// connection, payload is optional flags
|
||||
Packet Connect (char const *agent, char const *ident,
|
||||
size_t alen = ~size_t (0), size_t ilen = ~size_t (0));
|
||||
/// std::string wrapper for connection
|
||||
/// @param agent compiler identification
|
||||
/// @param ident compilation identification
|
||||
Packet Connect (std::string const &agent, std::string const &ident)
|
||||
{
|
||||
return Connect (agent.c_str (), ident.c_str (),
|
||||
agent.size (), ident.size ());
|
||||
}
|
||||
|
||||
public:
|
||||
/// Request compiler module repository
|
||||
/// @result packet indicating repo
|
||||
Packet ModuleRepo ();
|
||||
|
||||
public:
|
||||
/// Inform of compilation of a named module interface or partition,
|
||||
/// or a header unit
|
||||
/// @param str module or header-unit
|
||||
/// @param len name length, if known
|
||||
/// @result CMI name (or deferrment/error)
|
||||
Packet ModuleExport (char const *str, Flags flags, size_t len = ~size_t (0));
|
||||
|
||||
Packet ModuleExport (char const *str)
|
||||
{
|
||||
return ModuleExport (str, Flags::None, ~size_t (0));
|
||||
}
|
||||
Packet ModuleExport (std::string const &s, Flags flags = Flags::None)
|
||||
{
|
||||
return ModuleExport (s.c_str (), flags, s.size ());
|
||||
}
|
||||
|
||||
public:
|
||||
/// Importation of a module, partition or header-unit
|
||||
/// @param str module or header-unit
|
||||
/// @param len name length, if known
|
||||
/// @result CMI name (or deferrment/error)
|
||||
Packet ModuleImport (char const *str, Flags flags, size_t len = ~size_t (0));
|
||||
|
||||
Packet ModuleImport (char const *str)
|
||||
{
|
||||
return ModuleImport (str, Flags::None, ~size_t (0));
|
||||
}
|
||||
Packet ModuleImport (std::string const &s, Flags flags = Flags::None)
|
||||
{
|
||||
return ModuleImport (s.c_str (), flags, s.size ());
|
||||
}
|
||||
|
||||
public:
|
||||
/// Successful compilation of a module interface, partition or
|
||||
/// header-unit. Must have been preceeded by a ModuleExport
|
||||
/// request.
|
||||
/// @param str module or header-unit
|
||||
/// @param len name length, if known
|
||||
/// @result OK (or deferment/error)
|
||||
Packet ModuleCompiled (char const *str, Flags flags, size_t len = ~size_t (0));
|
||||
|
||||
Packet ModuleCompiled (char const *str)
|
||||
{
|
||||
return ModuleCompiled (str, Flags::None, ~size_t (0));
|
||||
}
|
||||
Packet ModuleCompiled (std::string const &s, Flags flags = Flags::None)
|
||||
{
|
||||
return ModuleCompiled (s.c_str (), flags, s.size ());
|
||||
}
|
||||
|
||||
/// Include translation query.
|
||||
/// @param str header unit name
|
||||
/// @param len name length, if known
|
||||
/// @result Packet indicating include translation boolean, or CMI
|
||||
/// name (or deferment/error)
|
||||
Packet IncludeTranslate (char const *str, Flags flags,
|
||||
size_t len = ~size_t (0));
|
||||
|
||||
Packet IncludeTranslate (char const *str)
|
||||
{
|
||||
return IncludeTranslate (str, Flags::None, ~size_t (0));
|
||||
}
|
||||
Packet IncludeTranslate (std::string const &s, Flags flags = Flags::None)
|
||||
{
|
||||
return IncludeTranslate (s.c_str (), flags, s.size ());
|
||||
}
|
||||
|
||||
public:
|
||||
/// Cork the connection. All requests are queued up. Each request
|
||||
/// call will return a PC_CORKED packet.
|
||||
void Cork ();
|
||||
|
||||
/// Uncork the connection. All queued requests are sent to the
|
||||
/// server, and a block of responses waited for.
|
||||
/// @result A vector of packets, containing the in-order responses to the
|
||||
/// queued requests.
|
||||
std::vector<Packet> Uncork ();
|
||||
///
|
||||
/// Indicate corkedness of connection
|
||||
bool IsCorked () const
|
||||
{
|
||||
return !corked.empty ();
|
||||
}
|
||||
|
||||
private:
|
||||
Packet ProcessResponse (std::vector<std::string> &, unsigned code,
|
||||
bool isLast);
|
||||
Packet MaybeRequest (unsigned code);
|
||||
int CommunicateWithServer ();
|
||||
};
|
||||
|
||||
/// This server-side class is used to resolve requests from one or
|
||||
/// more clients. You are expected to derive from it and override the
|
||||
/// virtual functions it provides. The connection resolver may return
|
||||
/// a different resolved object to service the remainder of the
|
||||
/// connection -- for instance depending on the compiler that is
|
||||
/// making the requests.
|
||||
class Resolver
|
||||
{
|
||||
public:
|
||||
Resolver () = default;
|
||||
virtual ~Resolver ();
|
||||
|
||||
protected:
|
||||
/// Mapping from a module or header-unit name to a CMI file name.
|
||||
/// @param module module name
|
||||
/// @result CMI name
|
||||
virtual std::string GetCMIName (std::string const &module);
|
||||
|
||||
/// Return the CMI file suffix to use
|
||||
/// @result CMI suffix, a statically allocated string
|
||||
virtual char const *GetCMISuffix ();
|
||||
|
||||
public:
|
||||
/// When the requests of a directly-connected server are processed,
|
||||
/// we may want to wait for the requests to complete (for instance a
|
||||
/// set of subjobs).
|
||||
/// @param s directly connected server.
|
||||
virtual void WaitUntilReady (Server *s);
|
||||
|
||||
public:
|
||||
/// Provide an error response.
|
||||
/// @param s the server to provide the response to.
|
||||
/// @param msg the error message
|
||||
virtual void ErrorResponse (Server *s, std::string &&msg);
|
||||
|
||||
public:
|
||||
/// Connection handshake. Provide response to server and return new
|
||||
/// (or current) resolver, or nullptr.
|
||||
/// @param s server to provide response to
|
||||
/// @param version the client's version number
|
||||
/// @param agent the client agent (compiler identification)
|
||||
/// @param ident the compilation identification (may be empty)
|
||||
/// @result nullptr in the case of an error. An error response will
|
||||
/// be sent. If handing off to another resolver, return that,
|
||||
/// otherwise this
|
||||
virtual Resolver *ConnectRequest (Server *s, unsigned version,
|
||||
std::string &agent, std::string &ident);
|
||||
|
||||
public:
|
||||
// return 0 on ok, ERRNO on failure, -1 on unspecific error
|
||||
virtual int ModuleRepoRequest (Server *s);
|
||||
|
||||
virtual int ModuleExportRequest (Server *s, Flags flags,
|
||||
std::string &module);
|
||||
virtual int ModuleImportRequest (Server *s, Flags flags,
|
||||
std::string &module);
|
||||
virtual int ModuleCompiledRequest (Server *s, Flags flags,
|
||||
std::string &module);
|
||||
virtual int IncludeTranslateRequest (Server *s, Flags flags,
|
||||
std::string &include);
|
||||
};
|
||||
|
||||
|
||||
/// This server-side (build system) class handles a single connection
|
||||
/// to a client. It has 3 states, READING:accumulating a message
|
||||
/// block froma client, WRITING:writing a message block to a client
|
||||
/// and PROCESSING:resolving requests. If the server does not spawn
|
||||
/// jobs to build needed artifacts, the PROCESSING state will be brief.
|
||||
class Server
|
||||
{
|
||||
public:
|
||||
enum Direction
|
||||
{
|
||||
READING, // Server is waiting for completion of a (set of)
|
||||
// requests from client. The next state will be PROCESSING.
|
||||
WRITING, // Server is writing a (set of) responses to client.
|
||||
// The next state will be READING.
|
||||
PROCESSING // Server is processing client request(s). The next
|
||||
// state will be WRITING.
|
||||
};
|
||||
|
||||
private:
|
||||
Detail::MessageBuffer write;
|
||||
Detail::MessageBuffer read;
|
||||
Resolver *resolver;
|
||||
Detail::FD fd;
|
||||
bool is_connected = false;
|
||||
Direction direction : 2;
|
||||
|
||||
public:
|
||||
Server (Resolver *r);
|
||||
Server (Resolver *r, int from, int to = -1)
|
||||
: Server (r)
|
||||
{
|
||||
fd.from = from;
|
||||
fd.to = to >= 0 ? to : from;
|
||||
}
|
||||
~Server ();
|
||||
Server (Server &&);
|
||||
Server &operator= (Server &&);
|
||||
|
||||
public:
|
||||
bool IsConnected () const
|
||||
{
|
||||
return is_connected;
|
||||
}
|
||||
|
||||
public:
|
||||
void SetDirection (Direction d)
|
||||
{
|
||||
direction = d;
|
||||
}
|
||||
|
||||
public:
|
||||
Direction GetDirection () const
|
||||
{
|
||||
return direction;
|
||||
}
|
||||
int GetFDRead () const
|
||||
{
|
||||
return fd.from;
|
||||
}
|
||||
int GetFDWrite () const
|
||||
{
|
||||
return fd.to;
|
||||
}
|
||||
Resolver *GetResolver () const
|
||||
{
|
||||
return resolver;
|
||||
}
|
||||
|
||||
public:
|
||||
/// Process requests from a directly-connected client. This is a
|
||||
/// small wrapper around ProcessRequests, with some buffer swapping
|
||||
/// for communication. It is expected that such processessing is
|
||||
/// immediate.
|
||||
/// @param from message block from client
|
||||
/// @param to message block to client
|
||||
void DirectProcess (Detail::MessageBuffer &from, Detail::MessageBuffer &to);
|
||||
|
||||
public:
|
||||
/// Process the messages queued in the read buffer. We enter the
|
||||
/// PROCESSING state, and each message line causes various resolver
|
||||
/// methods to be called. Once processed, the server may need to
|
||||
/// wait for all the requests to be ready, or it may be able to
|
||||
/// immediately write responses back.
|
||||
void ProcessRequests ();
|
||||
|
||||
public:
|
||||
/// Accumulate an error response.
|
||||
/// @param error the error message to encode
|
||||
/// @param elen length of error, if known
|
||||
void ErrorResponse (char const *error, size_t elen = ~size_t (0));
|
||||
void ErrorResponse (std::string const &error)
|
||||
{
|
||||
ErrorResponse (error.data (), error.size ());
|
||||
}
|
||||
|
||||
/// Accumulate an OK response
|
||||
void OKResponse ();
|
||||
|
||||
/// Accumulate a boolean response
|
||||
void BoolResponse (bool);
|
||||
|
||||
/// Accumulate a pathname response
|
||||
/// @param path (may be nullptr, or empty)
|
||||
/// @param rlen length, if known
|
||||
void PathnameResponse (char const *path, size_t plen = ~size_t (0));
|
||||
void PathnameResponse (std::string const &path)
|
||||
{
|
||||
PathnameResponse (path.data (), path.size ());
|
||||
}
|
||||
|
||||
public:
|
||||
/// Accumulate a (successful) connection response
|
||||
/// @param agent the server-side agent
|
||||
/// @param alen agent length, if known
|
||||
void ConnectResponse (char const *agent, size_t alen = ~size_t (0));
|
||||
void ConnectResponse (std::string const &agent)
|
||||
{
|
||||
ConnectResponse (agent.data (), agent.size ());
|
||||
}
|
||||
|
||||
public:
|
||||
/// Write message block to client. Semantics as for
|
||||
/// MessageBuffer::Write.
|
||||
/// @result errno or completion (0).
|
||||
int Write ()
|
||||
{
|
||||
return write.Write (fd.to);
|
||||
}
|
||||
/// Initialize for writing a message block. All responses to the
|
||||
/// incomping message block must be complete Enters WRITING state.
|
||||
void PrepareToWrite ()
|
||||
{
|
||||
write.PrepareToWrite ();
|
||||
direction = WRITING;
|
||||
}
|
||||
|
||||
public:
|
||||
/// Read message block from client. Semantics as for
|
||||
/// MessageBuffer::Read.
|
||||
/// @result errno, eof (-1) or completion (0)
|
||||
int Read ()
|
||||
{
|
||||
return read.Read (fd.from);
|
||||
}
|
||||
/// Initialize for reading a message block. Enters READING state.
|
||||
void PrepareToRead ()
|
||||
{
|
||||
read.PrepareToRead ();
|
||||
direction = READING;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper network stuff
|
||||
|
||||
#if CODY_NETWORKING
|
||||
// Socket with specific address
|
||||
int OpenSocket (char const **, sockaddr const *sock, socklen_t len);
|
||||
int ListenSocket (char const **, sockaddr const *sock, socklen_t len,
|
||||
unsigned backlog);
|
||||
|
||||
// Local domain socket (eg AF_UNIX)
|
||||
int OpenLocal (char const **, char const *name);
|
||||
int ListenLocal (char const **, char const *name, unsigned backlog = 0);
|
||||
|
||||
// ipv6 socket
|
||||
int OpenInet6 (char const **e, char const *name, int port);
|
||||
int ListenInet6 (char const **, char const *name, int port,
|
||||
unsigned backlog = 0);
|
||||
#endif
|
||||
|
||||
// FIXME: Mapping file utilities?
|
||||
|
||||
}
|
||||
|
||||
#endif // CODY_HH
|
29
libcody/config.h.in
Normal file
29
libcody/config.h.in
Normal file
@ -0,0 +1,29 @@
|
||||
/* config.h.in. Generated from configure.ac by autoheader. */
|
||||
|
||||
/* Bug reporting location */
|
||||
#undef BUGURL
|
||||
|
||||
/* Enable checking */
|
||||
#undef NMS_CHECKING
|
||||
|
||||
/* Define to the address where bug reports for this package should be sent. */
|
||||
#undef PACKAGE_BUGREPORT
|
||||
|
||||
/* Define to the full name of this package. */
|
||||
#undef PACKAGE_NAME
|
||||
|
||||
/* Define to the full name and version of this package. */
|
||||
#undef PACKAGE_STRING
|
||||
|
||||
/* Define to the one symbol short name of this package. */
|
||||
#undef PACKAGE_TARNAME
|
||||
|
||||
/* Define to the home page for this package. */
|
||||
#undef PACKAGE_URL
|
||||
|
||||
/* Define to the version of this package. */
|
||||
#undef PACKAGE_VERSION
|
||||
|
||||
#undef _FORTIFY_SOURCE
|
||||
|
||||
#define _GNU_SOURCE 1
|
280
libcody/config.m4
Normal file
280
libcody/config.m4
Normal file
@ -0,0 +1,280 @@
|
||||
# Nathan's Common Config -*- mode:autoconf -*-
|
||||
# Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
# License: Apache v2.0
|
||||
|
||||
AC_DEFUN([NMS_NOT_IN_SOURCE],
|
||||
[if test -e configure ; then
|
||||
AC_MSG_ERROR([Do not build in the source tree. Reasons])
|
||||
fi])
|
||||
|
||||
# thanks to Zack Weinberg for fixing this!
|
||||
AC_DEFUN([NMS_TOOLS],
|
||||
[AC_SUBST([tools], [])
|
||||
AC_ARG_WITH([tools],
|
||||
AS_HELP_STRING([--with-tools=DIR],[tool directory]),
|
||||
[AS_CASE([$withval],
|
||||
[yes], [AC_MSG_ERROR([--with-tools requires an argument])],
|
||||
[no], [:],
|
||||
[tools="${withval%/bin}"])])
|
||||
|
||||
if test -n "$tools" ; then
|
||||
if test -d "$tools/bin"; then
|
||||
PATH="$tools/bin:$PATH"
|
||||
AC_MSG_NOTICE([Using tools in $tools])
|
||||
else
|
||||
AC_MSG_ERROR([tool location does not exist])
|
||||
fi
|
||||
fi])
|
||||
|
||||
AC_DEFUN([NMS_TOOL_DIRS],
|
||||
[if test "$tools" && test -d "$tools/include" ; then
|
||||
CXX+=" -I$tools/include"
|
||||
fi
|
||||
if test "$tools" && test -d "$tools/lib" ; then
|
||||
toollib="$tools/lib"
|
||||
if os=$(CXX -print-multi-os-directory 2>/dev/null) ; then
|
||||
toollib+="/${os}"
|
||||
fi
|
||||
LDFLAGS+=" -L $toollib"
|
||||
unset toollib
|
||||
fi])
|
||||
|
||||
AC_DEFUN([NMS_NUM_CPUS],
|
||||
[AC_MSG_CHECKING([number of CPUs])
|
||||
AS_CASE([$build],
|
||||
[*-*-darwin*], [NUM_CPUS=$(sysctl -n hw.ncpu 2>/dev/null)],
|
||||
[NUM_CPUS=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null)])
|
||||
test "$NUM_CPUS" = 0 && NUM_CPUS=
|
||||
AC_MSG_RESULT([${NUM_CPUS:-unknown}])
|
||||
test "$NUM_CPUS" = 1 && NUM_CPUS=
|
||||
AC_SUBST(NUM_CPUS)])
|
||||
|
||||
AC_DEFUN([NMS_MAINTAINER_MODE],
|
||||
[AC_ARG_ENABLE([maintainer-mode],
|
||||
AS_HELP_STRING([--enable-maintainer-mode],
|
||||
[enable maintainer mode. Add rules to rebuild configurey bits]),,
|
||||
[enable_maintainer_mode=no])
|
||||
AS_CASE([$enable_maintainer_mode],
|
||||
[yes], [maintainer_mode=yes],
|
||||
[no], [maintainer=no],
|
||||
[AC_MSG_ERROR([unknown maintainer mode $enable_maintainer_mode])])
|
||||
AC_MSG_CHECKING([maintainer-mode])
|
||||
AC_MSG_RESULT([$maintainer_mode])
|
||||
test "$maintainer_mode" = yes && MAINTAINER=yes
|
||||
AC_SUBST(MAINTAINER)])
|
||||
|
||||
AC_DEFUN([NMS_CXX_COMPILER],
|
||||
[AC_ARG_WITH([compiler],
|
||||
AS_HELP_STRING([--with-compiler=NAME],[which compiler to use]),
|
||||
AC_MSG_CHECKING([C++ compiler])
|
||||
if test "$withval" = "yes" ; then
|
||||
AC_MSG_ERROR([NAME not specified])
|
||||
elif test "$withval" = "no" ; then
|
||||
AC_MSG_ERROR([Gonna need a C++ compiler!])
|
||||
else
|
||||
CXX="${withval}"
|
||||
AC_MSG_RESULT([$CXX])
|
||||
fi)])
|
||||
|
||||
AC_DEFUN([NMS_CXX_11],
|
||||
[AC_MSG_CHECKING([whether $CXX is for C++11])
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
|
||||
[#if __cplusplus != 201103
|
||||
#error "C++11 is required"
|
||||
#endif
|
||||
]])],
|
||||
[AC_MSG_RESULT([yes])],
|
||||
[CXX_ORIG="$CXX"
|
||||
CXX+=" -std=c++11"
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
|
||||
[#if __cplusplus != 201103
|
||||
#error "C++11 is required"
|
||||
#endif
|
||||
]])],
|
||||
AC_MSG_RESULT([adding -std=c++11]),
|
||||
[CXX="$CXX_ORIG"
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
|
||||
[#if __cplusplus > 201103
|
||||
#error "C++11 is required"
|
||||
#endif
|
||||
]])],
|
||||
AC_MSG_RESULT([> C++11]),
|
||||
AC_MSG_RESULT([no])
|
||||
AC_MSG_ERROR([C++11 is required])]))
|
||||
unset CXX_ORIG])])
|
||||
|
||||
AC_DEFUN([NMS_CXX_20],
|
||||
[AC_MSG_CHECKING([whether $CXX is for C++20])
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
|
||||
[#if __cplusplus <= 201703
|
||||
#error "C++20 is required"
|
||||
#endif
|
||||
]])],
|
||||
[AC_MSG_RESULT([yes])],
|
||||
[CXX+=" -std=c++20"
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
|
||||
[#if __cplusplus <= 201703
|
||||
#error "C++20 is required"
|
||||
#endif
|
||||
]])],
|
||||
AC_MSG_RESULT([adding -std=c++20]),
|
||||
AC_MSG_RESULT([no])
|
||||
AC_MSG_ERROR([C++20 is required])]))
|
||||
|
||||
AC_MSG_CHECKING([whether C++20 support is sufficiently advanced])
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
|
||||
#include <version>
|
||||
// There doesn't seem to be a feature macro for __VA_OPT__ :(
|
||||
#define VARIADIC(X,...) X __VA_OPT__((__VA_ARGS__))
|
||||
#define X(Y,Z) 1
|
||||
int ary[VARIADIC(X,Y,Z)];
|
||||
#if __cpp_constinit < 201907
|
||||
#error "C++20 constinit required"
|
||||
cpp_constinit is __cpp_constinit
|
||||
#endif
|
||||
#if __cpp_if_constexpr < 201606
|
||||
#error "C++20 constexpr required"
|
||||
cpp_constexpr is __cpp_if_constexpr
|
||||
#endif
|
||||
#if __cpp_concepts < 201907
|
||||
#error "C++20 concepts required"
|
||||
cpp_concepts is __cpp_concepts
|
||||
#endif
|
||||
#if __cpp_structured_bindings < 201606
|
||||
#error "C++20 structured bindings required"
|
||||
cpp_structured_bindings is __cpp_structured_bindings
|
||||
#endif
|
||||
#if __cpp_lib_int_pow2 < 202002
|
||||
#error "std::has_single_bit required"
|
||||
cpp_lib_int_pow2 is __cpp_lib_int_pow2
|
||||
#endif
|
||||
]])],
|
||||
AC_MSG_RESULT([yes 🙂]),
|
||||
AC_MSG_RESULT([no 🙁])
|
||||
AC_MSG_ERROR([C++20 support is too immature]))])
|
||||
|
||||
AC_DEFUN([NMS_ENABLE_EXCEPTIONS],
|
||||
[AC_ARG_ENABLE([exceptions],
|
||||
AS_HELP_STRING([--enable-exceptions],
|
||||
[enable exceptions & rtti]),,
|
||||
[enable_exceptions="no"])
|
||||
AS_CASE([$enable_exceptions],
|
||||
[yes], [nms_exceptions=yes],
|
||||
[no], [nms_exceptions=no],
|
||||
[AC_MSG_ERROR([unknown exceptions $enable_exceptions])])
|
||||
AC_MSG_CHECKING([exceptions])
|
||||
AC_MSG_RESULT([$nms_exceptions])
|
||||
if test "$nms_exceptions" != no ; then
|
||||
EXCEPTIONS=yes
|
||||
fi
|
||||
AC_SUBST(EXCEPTIONS)])
|
||||
|
||||
AC_DEFUN([NMS_LINK_OPT],
|
||||
[AC_MSG_CHECKING([adding $1 to linker])
|
||||
ORIG_LDFLAGS="$LDFLAGS"
|
||||
LDFLAGS+=" $1"
|
||||
AC_LINK_IFELSE([AC_LANG_PROGRAM([])],
|
||||
[AC_MSG_RESULT([ok])],
|
||||
[LDFLAGS="$ORIG_LDFLAGS"
|
||||
AC_MSG_RESULT([no])])
|
||||
unset ORIG_LDFLAGS])
|
||||
|
||||
AC_DEFUN([NMS_BUGURL],
|
||||
[AC_MSG_CHECKING([bugurl])
|
||||
AC_ARG_WITH(bugurl,
|
||||
AS_HELP_STRING([--with-bugurl=URL],[where to report bugs]),
|
||||
AS_CASE(["$withval"],
|
||||
[yes], [AC_MSG_ERROR([--with-bugurl requires an argument])],
|
||||
[no], [BUGURL=""],
|
||||
[BUGURL="${withval}"]),
|
||||
[BUGURL="${PACKAGE_BUGREPORT}"])
|
||||
AC_MSG_RESULT($BUGURL)
|
||||
AC_DEFINE_UNQUOTED(BUGURL,"$BUGURL",[Bug reporting location])])
|
||||
|
||||
AC_DEFUN([NMS_DISTRIBUTION],
|
||||
[AC_ARG_ENABLE([distribution],
|
||||
AS_HELP_STRING([--enable-distribution],
|
||||
[enable distribution. Inhibit components that prevent distribution]),,
|
||||
[enable_distribution="no"])
|
||||
AS_CASE([$enable_distribution],
|
||||
[yes], [nms_distribution=yes],
|
||||
[no], [nms_distribution=no],
|
||||
[AC_MSG_ERROR([unknown distribution $enable_distribution])])
|
||||
AC_MSG_CHECKING([distribution])
|
||||
AC_MSG_RESULT([$nms_distribution])])
|
||||
|
||||
AC_DEFUN([NMS_ENABLE_CHECKING],
|
||||
[AC_ARG_ENABLE([checking],
|
||||
AS_HELP_STRING([--enable-checking],
|
||||
[enable run-time checking]),,
|
||||
[enable_checking="yes"])
|
||||
AS_CASE([$enable_checking],
|
||||
[yes|all|yes,*], [nms_checking=yes],
|
||||
[no|none|release], [nms_checking=],
|
||||
[AC_MSG_ERROR([unknown check "$enable_checking"])])
|
||||
AC_MSG_CHECKING([checking])
|
||||
AC_MSG_RESULT([${nms_checking:-no}])
|
||||
if test "$nms_checking" = yes ; then
|
||||
AC_DEFINE_UNQUOTED([NMS_CHECKING], [0${nms_checking:+1}], [Enable checking])
|
||||
fi])
|
||||
|
||||
AC_DEFUN([NMS_WITH_BINUTILS],
|
||||
[AC_MSG_CHECKING([binutils])
|
||||
AC_ARG_WITH(bfd,
|
||||
AS_HELP_STRING([--with-bfd=DIR], [location of libbfd]),
|
||||
if test "$withval" = "yes" ; then
|
||||
AC_MSG_ERROR([DIR not specified])
|
||||
elif test "$withval" = "no" ; then
|
||||
AC_MSG_RESULT(installed)
|
||||
else
|
||||
AC_MSG_RESULT(${withval})
|
||||
CPPFLAGS+=" -I${withval}/include"
|
||||
LDFLAGS+=" -L${withval}/lib"
|
||||
fi,
|
||||
AC_MSG_RESULT(installed))])
|
||||
|
||||
AC_DEFUN([NMS_ENABLE_BACKTRACE],
|
||||
[AC_REQUIRE([NMS_DISTRIBUTION])
|
||||
AC_ARG_ENABLE([backtrace],
|
||||
AS_HELP_STRING([--enable-backtrace],[provide backtrace on fatality.]),,
|
||||
[enable_backtrace="maybe"])
|
||||
if test "${enable_backtrace:-maybe}" != no ; then
|
||||
AC_CHECK_HEADERS(execinfo.h)
|
||||
AC_CHECK_FUNCS(backtrace)
|
||||
if test "$nms_distribution" = no ; then
|
||||
AC_DEFINE([HAVE_DECL_BASENAME], [1], [Needed for demangle.h])
|
||||
# libiberty prevents distribution because of licensing
|
||||
AC_CHECK_HEADERS([demangle.h libiberty/demangle.h],[break])
|
||||
# libbfd prevents distribution because of licensing
|
||||
AC_CHECK_HEADERS([bfd.h])
|
||||
AC_SEARCH_LIBS([bfd_openr],[bfd],[LIBS+="-lz -liberty -ldl"],,[-lz -liberty -ldl])
|
||||
fi
|
||||
if test "$ac_cv_func_backtrace" = yes ; then
|
||||
nms_backtrace=yes
|
||||
ldbacktrace=-rdynamic
|
||||
AC_DEFINE([NMS_BACKTRACE], [1], [Enable backtrace])
|
||||
elif test "$enable_backtrace" = yes ; then
|
||||
AC_MSG_ERROR([Backtrace unavailable])
|
||||
fi
|
||||
AC_SUBST([ldbacktrace])
|
||||
fi
|
||||
AC_MSG_CHECKING([backtrace])
|
||||
AC_MSG_RESULT([${nms_backtrace:-no}])])
|
||||
|
||||
AC_DEFUN([NMS_CONFIG_FILES],
|
||||
[CONFIG_FILES="Makefile $1"
|
||||
SUBDIRS="$2"
|
||||
for generated in config.h.in configure ; do
|
||||
if test $srcdir/configure.ac -nt $srcdir/$generated ; then
|
||||
touch $srcdir/$generated
|
||||
fi
|
||||
done
|
||||
for dir in . $SUBDIRS
|
||||
do
|
||||
CONFIG_FILES+=" $dir/Makesub"
|
||||
test -f ${srcdir}/$dir/tests/Makesub.in && CONFIG_FILES+=" $dir/tests/Makesub"
|
||||
done
|
||||
AC_CONFIG_FILES([$CONFIG_FILES])
|
||||
AC_SUBST(configure_args,[$ac_configure_args])
|
||||
AC_SUBST(SUBDIRS)
|
||||
AC_SUBST(CONFIG_FILES)])
|
4201
libcody/configure
vendored
Executable file
4201
libcody/configure
vendored
Executable file
File diff suppressed because it is too large
Load Diff
37
libcody/configure.ac
Normal file
37
libcody/configure.ac
Normal file
@ -0,0 +1,37 @@
|
||||
# CODYlib -*- mode:autoconf -*-
|
||||
# Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
# License: Apache v2.0
|
||||
|
||||
AC_INIT([codylib],[0.0],[github.com/urnathan/libcody])
|
||||
AC_CONFIG_SRCDIR(cody.hh)
|
||||
m4_include(config.m4)
|
||||
|
||||
AC_CONFIG_AUX_DIR(build-aux)
|
||||
AC_SUBST(PACKAGE_VERSION)
|
||||
|
||||
NMS_NOT_IN_SOURCE
|
||||
AC_CANONICAL_HOST
|
||||
|
||||
NMS_TOOLS
|
||||
NMS_NUM_CPUS
|
||||
NMS_MAINTAINER_MODE
|
||||
NMS_CXX_COMPILER
|
||||
AC_LANG(C++)
|
||||
AC_PROG_CXX
|
||||
NMS_CXX_11
|
||||
NMS_TOOL_DIRS
|
||||
NMS_LINK_OPT([-Wl,--no-undefined])
|
||||
NMS_CONFIG_FILES([gdbinit dox.cfg])
|
||||
|
||||
NMS_BUGURL
|
||||
NMS_ENABLE_CHECKING
|
||||
NMS_ENABLE_EXCEPTIONS
|
||||
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AC_CHECK_TOOL([AR],[ar])
|
||||
AC_CHECK_PROG([DOXYGEN],[doxygen],[doxygen],[: NOTdoxygen])
|
||||
AC_CHECK_PROG([ALOY],[aloy],[aloy],[: Joust testsuite missing])
|
||||
AH_VERBATIM([_GNU_SOURCE],[#define _GNU_SOURCE 1])
|
||||
AH_VERBATIM([_FORTIFY_SOURCE],[#undef _FORTIFY_SOURCE])
|
||||
|
||||
AC_OUTPUT
|
2478
libcody/dox.cfg.in
Normal file
2478
libcody/dox.cfg.in
Normal file
File diff suppressed because it is too large
Load Diff
78
libcody/fatal.cc
Normal file
78
libcody/fatal.cc
Normal file
@ -0,0 +1,78 @@
|
||||
// CODYlib -*- mode:c++ -*-
|
||||
// Copyright (C) 2019-2020 Nathan Sidwell, nathan@acm.org
|
||||
// License: Apache v2.0
|
||||
|
||||
// Cody
|
||||
#include "internal.hh"
|
||||
// C
|
||||
#include <csignal>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace Cody {
|
||||
|
||||
#if NMS_CHECKING
|
||||
void (AssertFailed) (Location loc) noexcept
|
||||
{
|
||||
(HCF) ("assertion failed", loc);
|
||||
}
|
||||
void (Unreachable) (Location loc) noexcept
|
||||
{
|
||||
(HCF) ("unreachable reached", loc);
|
||||
}
|
||||
#endif
|
||||
|
||||
void (HCF) (char const *msg
|
||||
#if NMS_CHECKING
|
||||
, Location const loc
|
||||
#endif
|
||||
) noexcept
|
||||
{ // HCF - you goofed!
|
||||
__asm__ volatile ("nop"); // HCF - you goofed!
|
||||
|
||||
#if !NMS_CHECKING
|
||||
constexpr Location loc (nullptr, 0);
|
||||
#endif
|
||||
|
||||
fprintf (stderr, "CODYlib: %s", msg ? msg : "internal error");
|
||||
if (char const *file = loc.File ())
|
||||
{
|
||||
char const *src = SRCDIR;
|
||||
|
||||
if (src[0])
|
||||
{
|
||||
size_t l = strlen (src);
|
||||
|
||||
if (!strncmp (src, file, l) && file[l] == '/')
|
||||
file += l + 1;
|
||||
}
|
||||
fprintf (stderr, " at %s:%u", file, loc.Line ());
|
||||
}
|
||||
fprintf (stderr, "\n");
|
||||
raise (SIGABRT);
|
||||
exit (2);
|
||||
}
|
||||
|
||||
void BuildNote (FILE *stream) noexcept
|
||||
{
|
||||
fprintf (stream, "Version %s.\n", PACKAGE_NAME " " PACKAGE_VERSION);
|
||||
fprintf (stream, "Report bugs to %s.\n", BUGURL[0] ? BUGURL : "you");
|
||||
if (PACKAGE_URL[0])
|
||||
fprintf (stream, "See %s for more information.\n", PACKAGE_URL);
|
||||
if (REVISION[0])
|
||||
fprintf (stream, "Source %s.\n", REVISION);
|
||||
|
||||
fprintf (stream, "Build is %s & %s.\n",
|
||||
#if !NMS_CHECKING
|
||||
"un"
|
||||
#endif
|
||||
"checked",
|
||||
#if !__OPTIMIZE__
|
||||
"un"
|
||||
#endif
|
||||
"optimized");
|
||||
}
|
||||
|
||||
}
|
11
libcody/gdbinit.in
Normal file
11
libcody/gdbinit.in
Normal file
@ -0,0 +1,11 @@
|
||||
# CODYlib -*- mode:autoconf -*-
|
||||
# Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
# License: Apache v2.0
|
||||
|
||||
dir @srcdir@
|
||||
# some default breakpoints, to catch the fatal exit
|
||||
set breakpoint pending on
|
||||
break HCF
|
||||
set complaints 0
|
||||
break exit
|
||||
set breakpoint pending off
|
125
libcody/internal.hh
Normal file
125
libcody/internal.hh
Normal file
@ -0,0 +1,125 @@
|
||||
// CODYlib -*- mode:c++ -*-
|
||||
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
// License: Apache v2.0
|
||||
|
||||
#include "cody.hh"
|
||||
|
||||
// C++
|
||||
#if __GNUC__ >= 10
|
||||
#define CODY_LOC_BUILTIN 1
|
||||
#elif !defined (__has_include)
|
||||
#elif __has_include (<source_location>)
|
||||
#include <source_location>
|
||||
#define CODY_LOC_SOURCE 1
|
||||
#endif
|
||||
// C
|
||||
#include <cstdio>
|
||||
|
||||
namespace Cody {
|
||||
|
||||
// Location is needed regardless of checking, to make the fatal
|
||||
// handler simpler
|
||||
class Location
|
||||
{
|
||||
protected:
|
||||
char const *file;
|
||||
unsigned line;
|
||||
|
||||
public:
|
||||
constexpr Location (char const *file_
|
||||
#if CODY_LOC_BUILTIN
|
||||
= __builtin_FILE ()
|
||||
#elif !CODY_LOC_SOURCE
|
||||
= nullptr
|
||||
#endif
|
||||
, unsigned line_
|
||||
#if CODY_LOC_BUILTIN
|
||||
= __builtin_LINE ()
|
||||
#elif !CODY_LOC_SOURCE
|
||||
= 0
|
||||
#endif
|
||||
)
|
||||
:file (file_), line (line_)
|
||||
{
|
||||
}
|
||||
|
||||
#if !CODY_LOC_BUILTIN && CODY_LOC_SOURCE
|
||||
constexpr Location (source_location loc = source_location::current ())
|
||||
: Location (loc.file (), loc.line ())
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
constexpr char const *File () const
|
||||
{
|
||||
return file;
|
||||
}
|
||||
constexpr unsigned Line () const
|
||||
{
|
||||
return line;
|
||||
}
|
||||
};
|
||||
|
||||
void HCF [[noreturn]]
|
||||
(
|
||||
char const *msg
|
||||
#if NMS_CHECKING
|
||||
, Location const = Location ()
|
||||
#if !CODY_LOC_BUILTIN && !CODY_LOC_SOURCE
|
||||
#define HCF(M) HCF ((M), Cody::Location (__FILE__, __LINE__))
|
||||
#endif
|
||||
#endif
|
||||
) noexcept;
|
||||
|
||||
#if NMS_CHECKING
|
||||
void AssertFailed [[noreturn]] (Location loc = Location ()) noexcept;
|
||||
void Unreachable [[noreturn]] (Location loc = Location ()) noexcept;
|
||||
#if !CODY_LOC_BUILTIN && !CODY_LOC_SOURCE
|
||||
#define AssertFailed() AssertFailed (Cody::Location (__FILE__, __LINE__))
|
||||
#define Unreachable() Unreachable (Cody::Location (__FILE__, __LINE__))
|
||||
#endif
|
||||
|
||||
// Do we have __VA_OPT__, alas no specific feature macro for it :(
|
||||
// From stack overflow
|
||||
// https://stackoverflow.com/questions/48045470/portably-detect-va-opt-support
|
||||
// Relies on having variadic macros, but they're a C++11 thing, so
|
||||
// we're good
|
||||
#define HAVE_ARG_3(a,b,c,...) c
|
||||
#define HAVE_VA_OPT_(...) HAVE_ARG_3(__VA_OPT__(,),true,false,)
|
||||
#define HAVE_VA_OPT HAVE_VA_OPT_(?)
|
||||
|
||||
// Oh, for lazily evaluated function parameters
|
||||
#if HAVE_VA_OPT
|
||||
// Assert is variadic, so you can write Assert (TPL<A,B>(C)) without
|
||||
// extraneous parens. I don't think we need that though.
|
||||
#define Assert(EXPR, ...) \
|
||||
(__builtin_expect (bool (EXPR __VA_OPT__ (, __VA_ARGS__)), true) \
|
||||
? (void)0 : AssertFailed ())
|
||||
#else
|
||||
// If you don't have the GNU ,##__VA_ARGS__ pasting extension, we'll
|
||||
// need another fallback
|
||||
#define Assert(EXPR, ...) \
|
||||
(__builtin_expect (bool (EXPR, ##__VA_ARGS__), true) \
|
||||
? (void)0 : AssertFailed ())
|
||||
#endif
|
||||
#else
|
||||
// Not asserting, use EXPR in an unevaluated context
|
||||
#if HAVE_VA_OPT
|
||||
#define Assert(EXPR, ...) \
|
||||
((void)sizeof (bool (EXPR __VA_OPT__ (, __VA_ARGS__))), (void)0)
|
||||
#else
|
||||
#define Assert(EXPR, ...) \
|
||||
((void)sizeof (bool (EXPR, ##__VA_ARGS__)), (void)0)
|
||||
#endif
|
||||
|
||||
inline void Unreachable () noexcept
|
||||
{
|
||||
__builtin_unreachable ();
|
||||
}
|
||||
#endif
|
||||
|
||||
// FIXME: This should be user visible in some way
|
||||
void BuildNote (FILE *stream) noexcept;
|
||||
|
||||
}
|
140
libcody/netclient.cc
Normal file
140
libcody/netclient.cc
Normal file
@ -0,0 +1,140 @@
|
||||
// CODYlib -*- mode:c++ -*-
|
||||
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
// License: Apache v2.0
|
||||
|
||||
// Cody
|
||||
#include "internal.hh"
|
||||
|
||||
#if CODY_NETWORKING
|
||||
// C
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
// OS
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#ifndef AI_NUMERICSERV
|
||||
#define AI_NUMERICSERV 0
|
||||
#endif
|
||||
|
||||
// Client-side networking helpers
|
||||
|
||||
namespace Cody {
|
||||
|
||||
int OpenSocket (char const **e, sockaddr const *addr, socklen_t len)
|
||||
{
|
||||
char const *errstr = nullptr;
|
||||
|
||||
int fd = socket (addr->sa_family, SOCK_STREAM, 0);
|
||||
if (fd < 0)
|
||||
{
|
||||
errstr = "creating socket";
|
||||
|
||||
fail:;
|
||||
int err = errno;
|
||||
if (e)
|
||||
*e = errstr;
|
||||
if (fd >= 0)
|
||||
close (fd);
|
||||
errno = err;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (connect (fd, addr, len) < 0)
|
||||
{
|
||||
errstr = "connecting socket";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int OpenLocal (char const **e, char const *name)
|
||||
{
|
||||
sockaddr_un addr;
|
||||
size_t len = strlen (name);
|
||||
|
||||
if (len >= sizeof (addr.sun_path))
|
||||
{
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset (&addr, 0, offsetof (sockaddr_un, sun_path));
|
||||
addr.sun_family = AF_UNIX;
|
||||
memcpy (addr.sun_path, name, len + 1);
|
||||
return OpenSocket (e, (sockaddr *)&addr, sizeof (addr));
|
||||
}
|
||||
|
||||
int OpenInet6 (char const **e, char const *name, int port)
|
||||
{
|
||||
addrinfo *addrs = nullptr;
|
||||
int fd = -1;
|
||||
char const *errstr = nullptr;
|
||||
|
||||
fd = socket (AF_INET6, SOCK_STREAM, 0);
|
||||
if (fd < 0)
|
||||
{
|
||||
errstr = "socket";
|
||||
|
||||
fail:;
|
||||
int err = errno;
|
||||
if (e)
|
||||
*e = errstr;
|
||||
if (fd >= 0)
|
||||
close (fd);
|
||||
if (addrs)
|
||||
freeaddrinfo (addrs);
|
||||
errno = err;
|
||||
return -1;
|
||||
}
|
||||
|
||||
addrinfo hints;
|
||||
hints.ai_flags = AI_NUMERICSERV;
|
||||
hints.ai_family = AF_INET6;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = 0;
|
||||
hints.ai_addrlen = 0;
|
||||
hints.ai_addr = nullptr;
|
||||
hints.ai_canonname = nullptr;
|
||||
hints.ai_next = nullptr;
|
||||
|
||||
/* getaddrinfo requires a port number, but is quite happy to accept
|
||||
invalid ones. So don't rely on it. */
|
||||
if (int err = getaddrinfo (name, "0", &hints, &addrs))
|
||||
{
|
||||
errstr = gai_strerror (err);
|
||||
// What's the best errno to set?
|
||||
errno = 0;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
sockaddr_in6 addr;
|
||||
memset (&addr, 0, sizeof (addr));
|
||||
addr.sin6_family = AF_INET6;
|
||||
|
||||
for (struct addrinfo *next = addrs; next; next = next->ai_next)
|
||||
if (next->ai_family == AF_INET6
|
||||
&& next->ai_socktype == SOCK_STREAM)
|
||||
{
|
||||
sockaddr_in6 *in6 = (sockaddr_in6 *)next->ai_addr;
|
||||
in6->sin6_port = htons (port);
|
||||
if (ntohs (in6->sin6_port) != port)
|
||||
errno = EINVAL;
|
||||
else if (!connect (fd, next->ai_addr, next->ai_addrlen))
|
||||
goto done;
|
||||
}
|
||||
errstr = "connecting";
|
||||
goto fail;
|
||||
|
||||
done:;
|
||||
freeaddrinfo (addrs);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
153
libcody/netserver.cc
Normal file
153
libcody/netserver.cc
Normal file
@ -0,0 +1,153 @@
|
||||
// CODYlib -*- mode:c++ -*-
|
||||
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
// License: Apache v2.0
|
||||
|
||||
// Cody
|
||||
#include "internal.hh"
|
||||
#if CODY_NETWORKING
|
||||
// C
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
// OS
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#ifndef AI_NUMERICSERV
|
||||
#define AI_NUMERICSERV 0
|
||||
#endif
|
||||
|
||||
// Server-side networking helpers
|
||||
|
||||
namespace Cody {
|
||||
|
||||
int ListenSocket (char const **e, sockaddr const *addr, socklen_t len,
|
||||
unsigned backlog)
|
||||
{
|
||||
char const *errstr = nullptr;
|
||||
|
||||
int fd = socket (addr->sa_family, SOCK_STREAM, 0);
|
||||
if (fd < 0)
|
||||
{
|
||||
errstr = "creating socket";
|
||||
|
||||
fail:;
|
||||
int err = errno;
|
||||
if (e)
|
||||
*e = errstr;
|
||||
if (fd >= 0)
|
||||
close (fd);
|
||||
errno = err;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (bind (fd, addr, len) < 0)
|
||||
{
|
||||
errstr = "binding socket";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (listen (fd, backlog ? backlog : 17) < 0)
|
||||
{
|
||||
errstr = "listening socket";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int ListenLocal (char const **e, char const *name, unsigned backlog)
|
||||
{
|
||||
sockaddr_un addr;
|
||||
size_t len = strlen (name);
|
||||
|
||||
if (len >= sizeof (addr.sun_path))
|
||||
{
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset (&addr, 0, offsetof (sockaddr_un, sun_path));
|
||||
addr.sun_family = AF_UNIX;
|
||||
memcpy (addr.sun_path, name, len + 1);
|
||||
|
||||
return ListenSocket (e, (sockaddr *)&addr, sizeof (addr), backlog);
|
||||
}
|
||||
|
||||
int ListenInet6 (char const **e, char const *name, int port, unsigned backlog)
|
||||
{
|
||||
addrinfo *addrs = nullptr;
|
||||
int fd = -1;
|
||||
char const *errstr = nullptr;
|
||||
|
||||
fd = socket (AF_INET6, SOCK_STREAM, 0);
|
||||
if (fd < 0)
|
||||
{
|
||||
errstr = "creating socket";
|
||||
|
||||
fail:;
|
||||
int err = errno;
|
||||
if (e)
|
||||
*e = errstr;
|
||||
if (fd >= 0)
|
||||
close (fd);
|
||||
if (addrs)
|
||||
freeaddrinfo (addrs);
|
||||
errno = err;
|
||||
return -1;
|
||||
}
|
||||
|
||||
addrinfo hints;
|
||||
hints.ai_flags = AI_NUMERICSERV;
|
||||
hints.ai_family = AF_INET6;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = 0;
|
||||
hints.ai_addrlen = 0;
|
||||
hints.ai_addr = nullptr;
|
||||
hints.ai_canonname = nullptr;
|
||||
hints.ai_next = nullptr;
|
||||
|
||||
/* getaddrinfo requires a port number, but is quite happy to accept
|
||||
invalid ones. So don't rely on it. */
|
||||
if (int err = getaddrinfo (name, "0", &hints, &addrs))
|
||||
{
|
||||
errstr = gai_strerror (err);
|
||||
// What's the best errno to set?
|
||||
errno = 0;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
sockaddr_in6 addr;
|
||||
memset (&addr, 0, sizeof (addr));
|
||||
addr.sin6_family = AF_INET6;
|
||||
|
||||
for (struct addrinfo *next = addrs; next; next = next->ai_next)
|
||||
if (next->ai_family == AF_INET6
|
||||
&& next->ai_socktype == SOCK_STREAM)
|
||||
{
|
||||
sockaddr_in6 *in6 = (sockaddr_in6 *)next->ai_addr;
|
||||
in6->sin6_port = htons (port);
|
||||
if (ntohs (in6->sin6_port) != port)
|
||||
errno = EINVAL;
|
||||
else if (!bind (fd, next->ai_addr, next->ai_addrlen))
|
||||
goto listen;
|
||||
}
|
||||
|
||||
errstr = "binding socket";
|
||||
goto fail;
|
||||
|
||||
listen:;
|
||||
freeaddrinfo (addrs);
|
||||
|
||||
if (listen (fd, backlog ? backlog : 17) < 0)
|
||||
{
|
||||
errstr = "listening socket";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
50
libcody/packet.cc
Normal file
50
libcody/packet.cc
Normal file
@ -0,0 +1,50 @@
|
||||
// CODYlib -*- mode:c++ -*-
|
||||
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
// License: Apache v2.0
|
||||
|
||||
// Cody
|
||||
#include "internal.hh"
|
||||
|
||||
namespace Cody {
|
||||
|
||||
void Packet::Destroy ()
|
||||
{
|
||||
switch (cat)
|
||||
{
|
||||
case STRING:
|
||||
// Silly scope destructor name rules
|
||||
using S = std::string;
|
||||
string.~S ();
|
||||
break;
|
||||
|
||||
case VECTOR:
|
||||
using V = std::vector<std::string>;
|
||||
vector.~V ();
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void Packet::Create (Packet &&t)
|
||||
{
|
||||
cat = t.cat;
|
||||
code = t.code;
|
||||
request = t.request;
|
||||
switch (cat)
|
||||
{
|
||||
case STRING:
|
||||
new (&string) std::string (std::move (t.string));
|
||||
break;
|
||||
|
||||
case VECTOR:
|
||||
new (&vector) std::vector<std::string> (std::move (t.vector));
|
||||
break;
|
||||
|
||||
default:
|
||||
integer = t.integer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
209
libcody/resolver.cc
Normal file
209
libcody/resolver.cc
Normal file
@ -0,0 +1,209 @@
|
||||
// CODYlib -*- mode:c++ -*-
|
||||
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
// License: Apache v2.0
|
||||
|
||||
// Cody
|
||||
#include "internal.hh"
|
||||
// OS
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#if (defined (__unix__) \
|
||||
|| (defined (__Apple__) \
|
||||
&& defined (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) \
|
||||
&& __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101000))
|
||||
// Autoconf test?
|
||||
#define HAVE_FSTATAT 1
|
||||
#else
|
||||
#define HAVE_FSTATAT 0
|
||||
#endif
|
||||
|
||||
// Resolver code
|
||||
|
||||
#if __windows__
|
||||
inline bool IsDirSep (char c)
|
||||
{
|
||||
return c == '/' || c == '\\';
|
||||
}
|
||||
inline bool IsAbsPath (char const *str)
|
||||
{
|
||||
// IIRC windows has the concept of per-drive current directories,
|
||||
// which make drive-using paths confusing. Let's not get into that.
|
||||
return IsDirSep (str)
|
||||
|| (((str[0] >= 'A' && str[1] <= 'Z')
|
||||
|| (str[0] >= 'a' && str[1] <= 'z'))&& str[1] == ':');
|
||||
}
|
||||
#else
|
||||
inline bool IsDirSep (char c)
|
||||
{
|
||||
return c == '/';
|
||||
}
|
||||
inline bool IsAbsPath (char const *str)
|
||||
{
|
||||
return IsDirSep (str[0]);
|
||||
}
|
||||
#endif
|
||||
|
||||
constexpr char DIR_SEPARATOR = '/';
|
||||
|
||||
constexpr char DOT_REPLACE = ','; // Replace . directories
|
||||
constexpr char COLON_REPLACE = '-'; // Replace : (partition char)
|
||||
constexpr char const REPO_DIR[] = "cmi.cache";
|
||||
|
||||
namespace Cody {
|
||||
|
||||
Resolver::~Resolver ()
|
||||
{
|
||||
}
|
||||
|
||||
char const *Resolver::GetCMISuffix ()
|
||||
{
|
||||
return "cmi";
|
||||
}
|
||||
|
||||
std::string Resolver::GetCMIName (std::string const &module)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
result.reserve (module.size () + 8);
|
||||
bool is_header = false;
|
||||
bool is_abs = false;
|
||||
|
||||
if (IsAbsPath (module.c_str ()))
|
||||
is_header = is_abs = true;
|
||||
else if (module.front () == '.' && IsDirSep (module.c_str ()[1]))
|
||||
is_header = true;
|
||||
|
||||
if (is_abs)
|
||||
{
|
||||
result.push_back ('.');
|
||||
result.append (module);
|
||||
}
|
||||
else
|
||||
result = std::move (module);
|
||||
|
||||
if (is_header)
|
||||
{
|
||||
if (!is_abs)
|
||||
result[0] = DOT_REPLACE;
|
||||
|
||||
/* Map .. to DOT_REPLACE, DOT_REPLACE. */
|
||||
for (size_t ix = 1; ; ix++)
|
||||
{
|
||||
ix = result.find ('.', ix);
|
||||
if (ix == result.npos)
|
||||
break;
|
||||
if (ix + 2 > result.size ())
|
||||
break;
|
||||
if (result[ix + 1] != '.')
|
||||
continue;
|
||||
if (!IsDirSep (result[ix - 1]))
|
||||
continue;
|
||||
if (!IsDirSep (result[ix + 2]))
|
||||
continue;
|
||||
result[ix] = DOT_REPLACE;
|
||||
result[ix + 1] = DOT_REPLACE;
|
||||
}
|
||||
}
|
||||
else if (COLON_REPLACE != ':')
|
||||
{
|
||||
// There can only be one colon in a module name
|
||||
auto colon = result.find (':');
|
||||
if (colon != result.npos)
|
||||
result[colon] = COLON_REPLACE;
|
||||
}
|
||||
|
||||
if (char const *suffix = GetCMISuffix ())
|
||||
{
|
||||
result.push_back ('.');
|
||||
result.append (suffix);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Resolver::WaitUntilReady (Server *)
|
||||
{
|
||||
}
|
||||
|
||||
Resolver *Resolver::ConnectRequest (Server *s, unsigned version,
|
||||
std::string &, std::string &)
|
||||
{
|
||||
if (version > Version)
|
||||
s->ErrorResponse ("version mismatch");
|
||||
else
|
||||
s->ConnectResponse ("default");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
int Resolver::ModuleRepoRequest (Server *s)
|
||||
{
|
||||
s->PathnameResponse (REPO_DIR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Deprecated resolver functions
|
||||
int Resolver::ModuleExportRequest (Server *s, Flags, std::string &module)
|
||||
{
|
||||
auto cmi = GetCMIName (module);
|
||||
s->PathnameResponse (cmi);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Resolver::ModuleImportRequest (Server *s, Flags, std::string &module)
|
||||
{
|
||||
auto cmi = GetCMIName (module);
|
||||
s->PathnameResponse (cmi);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Resolver::ModuleCompiledRequest (Server *s, Flags, std::string &)
|
||||
{
|
||||
s->OKResponse ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Resolver::IncludeTranslateRequest (Server *s, Flags, std::string &include)
|
||||
{
|
||||
bool xlate = false;
|
||||
|
||||
// This is not the most efficient
|
||||
auto cmi = GetCMIName (include);
|
||||
struct stat statbuf;
|
||||
|
||||
#if HAVE_FSTATAT
|
||||
int fd_dir = open (REPO_DIR, O_RDONLY | O_CLOEXEC | O_DIRECTORY);
|
||||
if (fd_dir >= 0
|
||||
&& fstatat (fd_dir, cmi.c_str (), &statbuf, 0) == 0
|
||||
&& S_ISREG (statbuf.st_mode))
|
||||
// Sadly can't easily check if this process has read access,
|
||||
// except by trying to open it.
|
||||
xlate = true;
|
||||
if (fd_dir >= 0)
|
||||
close (fd_dir);
|
||||
#else
|
||||
std::string append = REPO_DIR;
|
||||
append.push_back (DIR_SEPARATOR);
|
||||
append.append (cmi);
|
||||
if (stat (append.c_str (), &statbuf) == 0
|
||||
|| S_ISREG (statbuf.st_mode))
|
||||
xlate = true;
|
||||
#endif
|
||||
|
||||
if (xlate)
|
||||
s->PathnameResponse (cmi);
|
||||
else
|
||||
s->BoolResponse (false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Resolver::ErrorResponse (Server *server, std::string &&msg)
|
||||
{
|
||||
server->ErrorResponse (msg);
|
||||
}
|
||||
|
||||
}
|
306
libcody/server.cc
Normal file
306
libcody/server.cc
Normal file
@ -0,0 +1,306 @@
|
||||
// CODYlib -*- mode:c++ -*-
|
||||
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
// License: Apache v2.0
|
||||
|
||||
// Cody
|
||||
#include "internal.hh"
|
||||
// C++
|
||||
#include <tuple>
|
||||
// C
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
// Server code
|
||||
|
||||
namespace Cody {
|
||||
|
||||
// These do not need to be members
|
||||
static Resolver *ConnectRequest (Server *, Resolver *,
|
||||
std::vector<std::string> &words);
|
||||
static int ModuleRepoRequest (Server *, Resolver *,
|
||||
std::vector<std::string> &words);
|
||||
static int ModuleExportRequest (Server *, Resolver *,
|
||||
std::vector<std::string> &words);
|
||||
static int ModuleImportRequest (Server *, Resolver *,
|
||||
std::vector<std::string> &words);
|
||||
static int ModuleCompiledRequest (Server *, Resolver *,
|
||||
std::vector<std::string> &words);
|
||||
static int IncludeTranslateRequest (Server *, Resolver *,
|
||||
std::vector<std::string> &words);
|
||||
|
||||
namespace {
|
||||
using RequestFn = int (Server *, Resolver *, std::vector<std::string> &);
|
||||
using RequestPair = std::tuple<char const *, RequestFn *>;
|
||||
static RequestPair
|
||||
const requestTable[Detail::RC_HWM] =
|
||||
{
|
||||
// Same order as enum RequestCode
|
||||
RequestPair {u8"HELLO", nullptr},
|
||||
RequestPair {u8"MODULE-REPO", ModuleRepoRequest},
|
||||
RequestPair {u8"MODULE-EXPORT", ModuleExportRequest},
|
||||
RequestPair {u8"MODULE-IMPORT", ModuleImportRequest},
|
||||
RequestPair {u8"MODULE-COMPILED", ModuleCompiledRequest},
|
||||
RequestPair {u8"INCLUDE-TRANSLATE", IncludeTranslateRequest},
|
||||
};
|
||||
}
|
||||
|
||||
Server::Server (Resolver *r)
|
||||
: resolver (r), direction (READING)
|
||||
{
|
||||
PrepareToRead ();
|
||||
}
|
||||
|
||||
Server::Server (Server &&src)
|
||||
: write (std::move (src.write)),
|
||||
read (std::move (src.read)),
|
||||
resolver (src.resolver),
|
||||
is_connected (src.is_connected),
|
||||
direction (src.direction)
|
||||
{
|
||||
fd.from = src.fd.from;
|
||||
fd.to = src.fd.to;
|
||||
}
|
||||
|
||||
Server::~Server ()
|
||||
{
|
||||
}
|
||||
|
||||
Server &Server::operator= (Server &&src)
|
||||
{
|
||||
write = std::move (src.write);
|
||||
read = std::move (src.read);
|
||||
resolver = src.resolver;
|
||||
is_connected = src.is_connected;
|
||||
direction = src.direction;
|
||||
fd.from = src.fd.from;
|
||||
fd.to = src.fd.to;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Server::DirectProcess (Detail::MessageBuffer &from,
|
||||
Detail::MessageBuffer &to)
|
||||
{
|
||||
read.PrepareToRead ();
|
||||
std::swap (read, from);
|
||||
ProcessRequests ();
|
||||
resolver->WaitUntilReady (this);
|
||||
write.PrepareToWrite ();
|
||||
std::swap (to, write);
|
||||
}
|
||||
|
||||
void Server::ProcessRequests (void)
|
||||
{
|
||||
std::vector<std::string> words;
|
||||
|
||||
direction = PROCESSING;
|
||||
while (!read.IsAtEnd ())
|
||||
{
|
||||
int err = 0;
|
||||
unsigned ix = Detail::RC_HWM;
|
||||
if (!read.Lex (words))
|
||||
{
|
||||
Assert (!words.empty ());
|
||||
while (ix--)
|
||||
{
|
||||
if (words[0] != std::get<0> (requestTable[ix]))
|
||||
continue; // not this one
|
||||
|
||||
if (ix == Detail::RC_CONNECT)
|
||||
{
|
||||
// CONNECT
|
||||
if (IsConnected ())
|
||||
err = -1;
|
||||
else if (auto *r = ConnectRequest (this, resolver, words))
|
||||
resolver = r;
|
||||
else
|
||||
err = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsConnected ())
|
||||
err = -1;
|
||||
else if (int res = (std::get<1> (requestTable[ix])
|
||||
(this, resolver, words)))
|
||||
err = res;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (err || ix >= Detail::RC_HWM)
|
||||
{
|
||||
// Some kind of error
|
||||
std::string msg;
|
||||
|
||||
if (err > 0)
|
||||
msg = u8"error processing '";
|
||||
else if (ix >= Detail::RC_HWM)
|
||||
msg = u8"unrecognized '";
|
||||
else if (IsConnected () && ix == Detail::RC_CONNECT)
|
||||
msg = u8"already connected '";
|
||||
else if (!IsConnected () && ix != Detail::RC_CONNECT)
|
||||
msg = u8"not connected '";
|
||||
else
|
||||
msg = u8"malformed '";
|
||||
|
||||
read.LexedLine (msg);
|
||||
msg.append (u8"'");
|
||||
if (err > 0)
|
||||
{
|
||||
msg.append (u8" ");
|
||||
msg.append (strerror (err));
|
||||
}
|
||||
resolver->ErrorResponse (this, std::move (msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return numeric value of STR as an unsigned. Returns ~0u on error
|
||||
// (so that value is not representable).
|
||||
static unsigned ParseUnsigned (std::string &str)
|
||||
{
|
||||
char *eptr;
|
||||
unsigned long val = strtoul (str.c_str (), &eptr, 10);
|
||||
if (*eptr || unsigned (val) != val)
|
||||
return ~0u;
|
||||
|
||||
return unsigned (val);
|
||||
}
|
||||
|
||||
Resolver *ConnectRequest (Server *s, Resolver *r,
|
||||
std::vector<std::string> &words)
|
||||
{
|
||||
if (words.size () < 3 || words.size () > 4)
|
||||
return nullptr;
|
||||
|
||||
if (words.size () == 3)
|
||||
words.emplace_back (u8"");
|
||||
unsigned version = ParseUnsigned (words[1]);
|
||||
if (version == ~0u)
|
||||
return nullptr;
|
||||
|
||||
return r->ConnectRequest (s, version, words[2], words[3]);
|
||||
}
|
||||
|
||||
int ModuleRepoRequest (Server *s, Resolver *r,std::vector<std::string> &words)
|
||||
{
|
||||
if (words.size () != 1)
|
||||
return -1;
|
||||
|
||||
return r->ModuleRepoRequest (s);
|
||||
}
|
||||
|
||||
int ModuleExportRequest (Server *s, Resolver *r, std::vector<std::string> &words)
|
||||
{
|
||||
if (words.size () < 2 || words.size () > 3 || words[1].empty ())
|
||||
return -1;
|
||||
|
||||
Flags flags = Flags::None;
|
||||
if (words.size () == 3)
|
||||
{
|
||||
unsigned val = ParseUnsigned (words[2]);
|
||||
if (val == ~0u)
|
||||
return -1;
|
||||
flags = Flags (val);
|
||||
}
|
||||
|
||||
return r->ModuleExportRequest (s, flags, words[1]);
|
||||
}
|
||||
|
||||
int ModuleImportRequest (Server *s, Resolver *r, std::vector<std::string> &words)
|
||||
{
|
||||
if (words.size () < 2 || words.size () > 3 || words[1].empty ())
|
||||
return -1;
|
||||
|
||||
Flags flags = Flags::None;
|
||||
if (words.size () == 3)
|
||||
{
|
||||
unsigned val = ParseUnsigned (words[2]);
|
||||
if (val == ~0u)
|
||||
return -1;
|
||||
flags = Flags (val);
|
||||
}
|
||||
|
||||
return r->ModuleImportRequest (s, flags, words[1]);
|
||||
}
|
||||
|
||||
int ModuleCompiledRequest (Server *s, Resolver *r,
|
||||
std::vector<std::string> &words)
|
||||
{
|
||||
if (words.size () < 2 || words.size () > 3 || words[1].empty ())
|
||||
return -1;
|
||||
|
||||
Flags flags = Flags::None;
|
||||
if (words.size () == 3)
|
||||
{
|
||||
unsigned val = ParseUnsigned (words[2]);
|
||||
if (val == ~0u)
|
||||
return -1;
|
||||
flags = Flags (val);
|
||||
}
|
||||
|
||||
return r->ModuleCompiledRequest (s, flags, words[1]);
|
||||
}
|
||||
|
||||
int IncludeTranslateRequest (Server *s, Resolver *r,
|
||||
std::vector<std::string> &words)
|
||||
{
|
||||
if (words.size () < 2 || words.size () > 3 || words[1].empty ())
|
||||
return -1;
|
||||
|
||||
Flags flags = Flags::None;
|
||||
if (words.size () == 3)
|
||||
{
|
||||
unsigned val = ParseUnsigned (words[2]);
|
||||
if (val == ~0u)
|
||||
return -1;
|
||||
flags = Flags (val);
|
||||
}
|
||||
|
||||
return r->IncludeTranslateRequest (s, flags, words[1]);
|
||||
}
|
||||
|
||||
void Server::ErrorResponse (char const *error, size_t elen)
|
||||
{
|
||||
write.BeginLine ();
|
||||
write.AppendWord (u8"ERROR");
|
||||
write.AppendWord (error, true, elen);
|
||||
write.EndLine ();
|
||||
}
|
||||
|
||||
void Server::OKResponse ()
|
||||
{
|
||||
write.BeginLine ();
|
||||
write.AppendWord (u8"OK");
|
||||
write.EndLine ();
|
||||
}
|
||||
|
||||
void Server::ConnectResponse (char const *agent, size_t alen)
|
||||
{
|
||||
is_connected = true;
|
||||
|
||||
write.BeginLine ();
|
||||
write.AppendWord (u8"HELLO");
|
||||
write.AppendInteger (Version);
|
||||
write.AppendWord (agent, true, alen);
|
||||
write.EndLine ();
|
||||
}
|
||||
|
||||
void Server::PathnameResponse (char const *cmi, size_t clen)
|
||||
{
|
||||
write.BeginLine ();
|
||||
write.AppendWord (u8"PATHNAME");
|
||||
write.AppendWord (cmi, true, clen);
|
||||
write.EndLine ();
|
||||
}
|
||||
|
||||
void Server::BoolResponse (bool truthiness)
|
||||
{
|
||||
write.BeginLine ();
|
||||
write.AppendWord (u8"BOOL");
|
||||
write.AppendWord (truthiness ? u8"TRUE" : u8"FALSE");
|
||||
write.EndLine ();
|
||||
}
|
||||
|
||||
}
|
30
libcody/tests/01-serialize/connect.cc
Normal file
30
libcody/tests/01-serialize/connect.cc
Normal file
@ -0,0 +1,30 @@
|
||||
|
||||
// Test client connection handshake
|
||||
// RUN: <<HELLO 1 TESTING
|
||||
// RUN: $subdir$stem | ezio -p OUT $test |& ezio -p ERR $test
|
||||
// RUN-END:
|
||||
|
||||
// OUT-NEXT:^HELLO {:[0-9]+} TEST IDENT$
|
||||
// OUT-NEXT:$EOF
|
||||
|
||||
// ERR-NEXT:Code:{:[0-9]+}$
|
||||
// ERR-NEXT:Version:1$
|
||||
// ERR-NEXT:$EOF
|
||||
|
||||
|
||||
// Cody
|
||||
#include "cody.hh"
|
||||
// C++
|
||||
#include <iostream>
|
||||
|
||||
using namespace Cody;
|
||||
|
||||
int main (int, char *[])
|
||||
{
|
||||
Client client (0, 1);
|
||||
|
||||
auto token = client.Connect ("TEST", "IDENT");
|
||||
|
||||
std::cerr << "Code:" << token.GetCode () << '\n';
|
||||
std::cerr << "Version:" << token.GetInteger () << '\n';
|
||||
}
|
73
libcody/tests/01-serialize/decoder.cc
Normal file
73
libcody/tests/01-serialize/decoder.cc
Normal file
@ -0,0 +1,73 @@
|
||||
// CODYlib -*- mode:c++ -*-
|
||||
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
// License: Apache v2.0
|
||||
|
||||
// RUN: <<bob 'frob dob''\nF\_b\20\61\\'
|
||||
// RUN: $subdir$stem |& ezio $test
|
||||
// CHECK-NEXT: ^line:0 word:0 'bob'
|
||||
// CHECK-NEXT: ^line:0 word:1 'frob dob$
|
||||
// CHECK-OPTION: matchSpace
|
||||
// CHECK-NEXT: ^F b a\'$
|
||||
// CHECK-NEXT: $EOF
|
||||
|
||||
/* RUN: <<line-1 word:1 ;
|
||||
RUN: <<'line 2' ;
|
||||
RUN: <<
|
||||
*/
|
||||
// RUN: $subdir$stem |& ezio -p CHECK2 $test
|
||||
// CHECK2-NEXT: line:0 word:0 'line-1'
|
||||
// CHECK2-NEXT: line:0 word:1 'word:1'
|
||||
// CHECK2-NEXT: line:1 word:0 'line 2'
|
||||
// CHECK2-NEXT: error:No
|
||||
// CHECK2-NEXT: $EOF
|
||||
|
||||
// RUN: <<'
|
||||
// RUN: $subdir$stem |& ezio -p CHECK3 $test
|
||||
// CHECK3-NEXT: error:Invalid argument
|
||||
// CHECK3-NEXT: line:0 word:0 '''
|
||||
// CHECK3-NEXT: $EOF
|
||||
|
||||
/* RUN: << ;
|
||||
RUN: <<'\g'
|
||||
*/
|
||||
// RUN: $subdir$stem |& ezio -p CHECK4 $test
|
||||
// CHECK4-NEXT: error:No
|
||||
// CHECK4-NEXT: error:Invalid argument
|
||||
// CHECK4-NEXT: line:1 word:0 ''\g''
|
||||
// CHECK4-NEXT: $EOF
|
||||
|
||||
// RUN-END:
|
||||
|
||||
// Cody
|
||||
#include "cody.hh"
|
||||
// C++
|
||||
#include <iostream>
|
||||
// C
|
||||
#include <cstring>
|
||||
|
||||
using namespace Cody;
|
||||
|
||||
int main (int, char *[])
|
||||
{
|
||||
Detail::MessageBuffer reader;
|
||||
|
||||
reader.PrepareToRead ();
|
||||
while (int e = reader.Read (0))
|
||||
if (e != EAGAIN && e != EINTR)
|
||||
break;
|
||||
|
||||
std::vector<std::string> words;
|
||||
for (unsigned line = 0; !reader.IsAtEnd (); line++)
|
||||
{
|
||||
if (int e = reader.Lex (words))
|
||||
std::cerr << "error:" << strerror (e) << '\n';
|
||||
for (unsigned ix = 0; ix != words.size (); ix++)
|
||||
{
|
||||
auto &word = words[ix];
|
||||
|
||||
std::cerr << "line:" << line << " word:" << ix
|
||||
<< " '" << word << "'\n";
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
48
libcody/tests/01-serialize/encoder.cc
Normal file
48
libcody/tests/01-serialize/encoder.cc
Normal file
@ -0,0 +1,48 @@
|
||||
// CODYlib -*- mode:c++ -*-
|
||||
// Copyright (C) 2020 Nathan Sidwell, nathan@acm.org
|
||||
// License: Apache v2.0
|
||||
|
||||
// Test message encoding, both string quoting and continuation lines
|
||||
|
||||
// RUN: $subdir$stem |& ezio $test
|
||||
// RUN-END:
|
||||
// The ¯ is utf8-encoded as c2 af
|
||||
// CHECK-NEXT: ^bob 'frob dob''\n¯\\'$
|
||||
// CHECK-NEXT: ^2 ;$
|
||||
// CHECK-NEXT: ^3$
|
||||
// CHECK-NEXT: $EOF
|
||||
|
||||
// Cody
|
||||
#include "cody.hh"
|
||||
|
||||
using namespace Cody;
|
||||
|
||||
int main (int, char *[])
|
||||
{
|
||||
Detail::MessageBuffer writer;
|
||||
|
||||
writer.BeginLine ();
|
||||
writer.AppendWord ("bob");
|
||||
writer.AppendWord ("frob dob", true);
|
||||
writer.Append ("\n\xc2\xaf\\", true);
|
||||
writer.EndLine ();
|
||||
|
||||
writer.PrepareToWrite ();
|
||||
while (int err = writer.Write (2))
|
||||
if (err != EAGAIN && err != EINTR)
|
||||
break;
|
||||
|
||||
writer.BeginLine ();
|
||||
writer.Append ("2", true);
|
||||
writer.EndLine ();
|
||||
writer.BeginLine ();
|
||||
writer.Append ("3", true);
|
||||
writer.EndLine ();
|
||||
|
||||
writer.PrepareToWrite ();
|
||||
while (int err = writer.Write (2))
|
||||
if (err != EAGAIN && err != EINTR)
|
||||
break;
|
||||
|
||||
return 0;
|
||||
}
|
97
libcody/tests/02-comms/client-1.cc
Normal file
97
libcody/tests/02-comms/client-1.cc
Normal file
@ -0,0 +1,97 @@
|
||||
|
||||
// Test client message round tripping
|
||||
/*
|
||||
RUN: <<HELLO 1 TESTING ;
|
||||
RUN: <<PATHNAME REPO ;
|
||||
RUN: <<PATHNAME biz/bar ;
|
||||
RUN: <<PATHNAME blob ;
|
||||
RUN: <<BOOL FALSE ;
|
||||
RUN: << BOOL TRUE ;
|
||||
RUN: << PATHNAME foo ;
|
||||
RUN: <<OK
|
||||
*/
|
||||
// RUN: $subdir$stem | ezio -p OUT $test |& ezio -p ERR $test
|
||||
// RUN-END:
|
||||
|
||||
/*
|
||||
OUT-NEXT:^HELLO {:[0-9]+} TEST IDENT ;$
|
||||
OUT-NEXT:^MODULE-REPO ;
|
||||
OUT-NEXT:^MODULE-EXPORT bar ;
|
||||
OUT-NEXT:^MODULE-IMPORT foo ;
|
||||
OUT-NEXT:^INCLUDE-TRANSLATE baz.frob ;
|
||||
OUT-NEXT:^INCLUDE-TRANSLATE ./corge ;
|
||||
OUT-NEXT:^INCLUDE-TRANSLATE ./quux ;
|
||||
OUT-NEXT:^MODULE-COMPILED bar
|
||||
*/
|
||||
// OUT-NEXT:$EOF
|
||||
|
||||
// ERR-NEXT:Code:1$
|
||||
// ERR-NEXT:Integer:1$
|
||||
// ERR-NEXT:Code:5$
|
||||
// ERR-NEXT:String:REPO$
|
||||
// ERR-NEXT:Code:5$
|
||||
// ERR-NEXT:String:biz/bar$
|
||||
// ERR-NEXT:Code:5$
|
||||
// ERR-NEXT:String:blob$
|
||||
// ERR-NEXT:Code:4$
|
||||
// ERR-NEXT:Integer:0$
|
||||
// ERR-NEXT:Code:4$
|
||||
// ERR-NEXT:Integer:1$
|
||||
// ERR-NEXT:Code:5$
|
||||
// ERR-NEXT:String:foo
|
||||
// ERR-NEXT:Code:3$
|
||||
// ERR-NEXT:Integer:
|
||||
// ERR-NEXT:$EOF
|
||||
|
||||
|
||||
// Cody
|
||||
#include "cody.hh"
|
||||
// C++
|
||||
#include <iostream>
|
||||
|
||||
using namespace Cody;
|
||||
|
||||
int main (int, char *[])
|
||||
{
|
||||
Client client (0, 1);
|
||||
|
||||
client.Cork ();
|
||||
if (client.Connect ("TEST", "IDENT").GetCode () != Client::PC_CORKED)
|
||||
std::cerr << "Not corked!\n";
|
||||
if (client.ModuleRepo ().GetCode () != Client::PC_CORKED)
|
||||
std::cerr << "Not corked!\n";
|
||||
if (client.ModuleExport ("bar").GetCode () != Client::PC_CORKED)
|
||||
std::cerr << "Not corked!\n";
|
||||
if (client.ModuleImport ("foo").GetCode () != Client::PC_CORKED)
|
||||
std::cerr << "Not corked!\n";
|
||||
if (client.IncludeTranslate ("baz.frob").GetCode () != Client::PC_CORKED)
|
||||
std::cerr << "Not corked!\n";
|
||||
if (client.IncludeTranslate ("./corge").GetCode () != Client::PC_CORKED)
|
||||
std::cerr << "Not corked!\n";
|
||||
if (client.IncludeTranslate ("./quux").GetCode () != Client::PC_CORKED)
|
||||
std::cerr << "Not corked!\n";
|
||||
if (client.ModuleCompiled ("bar").GetCode () != Client::PC_CORKED)
|
||||
std::cerr << "Not corked!\n";
|
||||
|
||||
auto result = client.Uncork ();
|
||||
for (auto iter = result.begin (); iter != result.end (); ++iter)
|
||||
{
|
||||
std::cerr << "Code:" << iter->GetCode () << '\n';
|
||||
switch (iter->GetCategory ())
|
||||
{
|
||||
case Packet::INTEGER:
|
||||
std::cerr << "Integer:" << iter->GetInteger () << '\n';
|
||||
break;
|
||||
case Packet::STRING:
|
||||
std::cerr << "String:" << iter->GetString () << '\n';
|
||||
break;
|
||||
case Packet::VECTOR:
|
||||
{
|
||||
auto const &v = iter->GetVector ();
|
||||
for (unsigned ix = 0; ix != v.size (); ix++)
|
||||
std::cerr << "Vector[" << ix << "]:" << v[ix] << '\n';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
76
libcody/tests/02-comms/pivot-1.cc
Normal file
76
libcody/tests/02-comms/pivot-1.cc
Normal file
@ -0,0 +1,76 @@
|
||||
|
||||
// Test resolver pivot
|
||||
|
||||
// RUN:<<HELLO 1 TEST IDENT ;
|
||||
// RUN:<<MODULE-REPO ;
|
||||
// RUN:<<HELLO 1 TEST IDENT
|
||||
// RUN: $subdir$stem | ezio -p OUT1 $test |& ezio -p ERR1 $test
|
||||
// OUT1-NEXT:HELLO 1 default ;
|
||||
// OUT1-NEXT:PATHNAME cmi.cache ;
|
||||
// OUT1-NEXT:ERROR 'already connected
|
||||
// OUT1-NEXT:$EOF
|
||||
// ERR1-NEXT:resolver is handler
|
||||
// ERR1-NEXT:$EOF
|
||||
|
||||
// RUN:<<MODULE-REPO ;
|
||||
// RUN:<<HELLO 1 TEST IDENT ;
|
||||
// RUN:<<MODULE-REPO
|
||||
// RUN: $subdir$stem | ezio -p OUT2 $test |& ezio -p ERR2 $test
|
||||
// OUT2-NEXT:ERROR 'not connected
|
||||
// OUT2-NEXT:HELLO 1 default ;
|
||||
// OUT2-NEXT:PATHNAME cmi.cache
|
||||
// OUT2-NEXT:$EOF
|
||||
// ERR2-NEXT:resolver is handler
|
||||
// ERR2-NEXT:$EOF
|
||||
|
||||
// RUN-END:
|
||||
#include "cody.hh"
|
||||
#include <iostream>
|
||||
|
||||
using namespace Cody;
|
||||
|
||||
class Handler : public Resolver
|
||||
{
|
||||
virtual Handler *ConnectRequest (Server *s, unsigned ,
|
||||
std::string &, std::string &)
|
||||
{
|
||||
ErrorResponse (s, "unexpected connect call");
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
Handler handler;
|
||||
|
||||
class Initial : public Resolver
|
||||
{
|
||||
virtual Handler *ConnectRequest (Server *s, unsigned v,
|
||||
std::string &agent, std::string &ident)
|
||||
{
|
||||
Resolver::ConnectRequest (s, v, agent, ident);
|
||||
return &handler;
|
||||
}
|
||||
};
|
||||
|
||||
Initial initial;
|
||||
|
||||
int main (int, char *[])
|
||||
{
|
||||
Server server (&initial, 0, 1);
|
||||
|
||||
while (int e = server.Read ())
|
||||
if (e != EAGAIN && e != EINTR)
|
||||
break;
|
||||
|
||||
server.ProcessRequests ();
|
||||
if (server.GetResolver () == &handler)
|
||||
std::cerr << "resolver is handler\n";
|
||||
else if (server.GetResolver () == &initial)
|
||||
std::cerr << "resolver is initial\n";
|
||||
else
|
||||
std::cerr << "resolver is surprising\n";
|
||||
|
||||
server.PrepareToWrite ();
|
||||
while (int e = server.Write ())
|
||||
if (e != EAGAIN && e != EINTR)
|
||||
break;
|
||||
}
|
68
libcody/tests/02-comms/server-1.cc
Normal file
68
libcody/tests/02-comms/server-1.cc
Normal file
@ -0,0 +1,68 @@
|
||||
|
||||
// Test server message round tripping
|
||||
/*
|
||||
RUN:<<HELLO 1 TEST IDENT ;
|
||||
RUN:<<MODULE-REPO ;
|
||||
RUN:<<MODULE-EXPORT bar ;
|
||||
RUN:<<MODULE-IMPORT foo ;
|
||||
RUN:<<NOT A COMMAND ;
|
||||
RUN:<<INCLUDE-TRANSLATE baz.frob ;
|
||||
RUN:<<INCLUDE-TRANSLATE ./quux ;
|
||||
RUN:<<MODULE-COMPILED bar ;
|
||||
RUN:<<MODULE-IMPORT ''
|
||||
*/
|
||||
// RUN: $subdir$stem | ezio -p OUT1 $test |& ezio -p ERR1 $test
|
||||
|
||||
// These all fail because there's nothing in the server interpretting stuff
|
||||
/*
|
||||
OUT1-NEXT: ^HELLO 1 default ;
|
||||
OUT1-NEXT: ^PATHNAME cmi.cache ;
|
||||
OUT1-NEXT: ^PATHNAME bar.cmi ;
|
||||
OUT1-NEXT: ^PATHNAME foo.cmi ;
|
||||
OUT1-NEXT: ^ERROR 'unrecognized \'NOT
|
||||
OUT1-NEXT: ^BOOL FALSE ;
|
||||
OUT1-NEXT: ^BOOL FALSE ;
|
||||
OUT1-NEXT: ^OK
|
||||
OUT1-NEXT: ^ERROR 'malformed
|
||||
*/
|
||||
// OUT1-NEXT:$EOF
|
||||
// ERR1-NEXT:$EOF
|
||||
|
||||
/*
|
||||
RUN:<<HELLO 1 TEST IDENT
|
||||
RUN:<<MODULE-REPO
|
||||
*/
|
||||
// RUN: $subdir$stem | ezio -p OUT2 $test |& ezio -p ERR2 $test
|
||||
/*
|
||||
OUT2-NEXT: ^HELLO 1 default
|
||||
*/
|
||||
// OUT2-NEXT:$EOF
|
||||
// ERR2-NEXT:$EOF
|
||||
|
||||
// RUN-END:
|
||||
|
||||
// Cody
|
||||
#include "cody.hh"
|
||||
// C++
|
||||
#include <iostream>
|
||||
|
||||
using namespace Cody;
|
||||
|
||||
int main (int, char *[])
|
||||
{
|
||||
Resolver r;
|
||||
Server server (&r, 0, 1);
|
||||
|
||||
while (int e = server.Read ())
|
||||
if (e != EAGAIN && e != EINTR)
|
||||
break;
|
||||
|
||||
server.ProcessRequests ();
|
||||
if (server.GetResolver () != &r)
|
||||
std::cerr << "resolver changed\n";
|
||||
server.PrepareToWrite ();
|
||||
|
||||
while (int e = server.Write ())
|
||||
if (e != EAGAIN && e != EINTR)
|
||||
break;
|
||||
}
|
36
libcody/tests/Makesub.in
Normal file
36
libcody/tests/Makesub.in
Normal file
@ -0,0 +1,36 @@
|
||||
# CODYlib -*- mode:Makefile -*-
|
||||
# Copyright (C) 2019-2020 Nathan Sidwell, nathan@acm.org
|
||||
# License: Apache v2.0
|
||||
|
||||
ALOY := @ALOY@
|
||||
TESTS := $(patsubst $(srcdir)/%.cc,%,\
|
||||
$(wildcard $(srcdir)/tests/*/*.cc))
|
||||
TESTDIRS = $(shell cd $(srcdir)/${<D} ; echo *(/))
|
||||
testdir := $(and $(filter-out /%,$(srcdir)),../)$(srcdir)/tests
|
||||
|
||||
check:: tests/cody.defs $(TESTS)
|
||||
+cd ${<D} && srcbuilddir=$(srcdir)/tests JOUST=${<F} \
|
||||
$(ALOY) -t kratos -o cody -g $(testdir)/jouster $(TESTDIRS)
|
||||
ifeq ($(firstword $(aloy)),:)
|
||||
@echo WARNING: tests were not run as Joust test harness was not found
|
||||
endif
|
||||
|
||||
tests/cody.defs: tests/Makesub
|
||||
echo '# Automatically generated by Make' >$@
|
||||
echo 'testdir=$(testdir)' >>$@
|
||||
echo 'timelimit=60' >>$@
|
||||
echo 'memlimit=1' >>$@
|
||||
echo 'cpulimit=60' >>$@
|
||||
echo 'filelimit=1' >>$@
|
||||
echo 'SHELL=$(SHELL)' >>$@
|
||||
|
||||
$(TESTS): %: %.o libcody.a
|
||||
$(CXX) $(LDFLAGS) $< -lcody $(LIBS) -o $@
|
||||
|
||||
clean::
|
||||
rm -f $(TESTS)
|
||||
rm -f $(TESTS:=.o) $(TESTS:=.d)
|
||||
|
||||
ifeq ($(filter clean%,$(MAKECMDGOALS)),)
|
||||
-include $(TESTS:=.d)
|
||||
endif
|
11
libcody/tests/jouster
Executable file
11
libcody/tests/jouster
Executable file
@ -0,0 +1,11 @@
|
||||
#! /bin/zsh
|
||||
# CODYlib -*- mode:Makefile -*-
|
||||
# Copyright (C) 2019-2020 Nathan Sidwell, nathan@acm.org
|
||||
# License Apache v2.0
|
||||
|
||||
pushd ${0%/*}
|
||||
setopt nullglob
|
||||
for subdir in $@ ; do
|
||||
echo $subdir/*(.^*)
|
||||
done
|
||||
popd
|
Loading…
Reference in New Issue
Block a user