Make checkperf work on Windows (#799)

* Make command line arguments work for Windows

* Run checkperf on Windows
This commit is contained in:
John Keiser 2020-04-27 11:20:05 -07:00 committed by GitHub
parent 0514588175
commit 0e6ea76e88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 915 additions and 234 deletions

View File

@ -1,41 +1,34 @@
version: '{build}'
branches:
only:
- master
clone_folder: c:\projects\simdjson
platform: x64
image:
- Visual Studio 2019
- Visual Studio 2017
branches: { only: [ master ] }
configuration: Release
image: Visual Studio 2019
platform: x64
environment:
matrix:
- SIMDJSON_BUILD_STATIC: ON
SIMDJSON_ENABLE_THREADS: OFF
SIMDJSON_PLATFORM: x64
- SIMDJSON_BUILD_STATIC: ON
SIMDJSON_ENABLE_THREADS: OFF
SIMDJSON_PLATFORM: Win32
- SIMDJSON_BUILD_STATIC: OFF
SIMDJSON_ENABLE_THREADS: ON
SIMDJSON_PLATFORM: x64
- job_name: VS2019
CMAKE_ARGS: -DSIMDJSON_CHECKPERF_BRANCH=jkeiser/parse-t
- job_name: VS2017 (Static, No Threads)
image: Visual Studio 2017
CMAKE_ARGS: -DSIMDJSON_BUILD_STATIC=ON -DSIMDJSON_ENABLE_THREADS=OFF
CTEST_ARGS: -E checkperf
- job_name: VS2019 (Win32)
platform: Win32
CMAKE_ARGS: -DSIMDJSON_BUILD_STATIC=OFF -DSIMDJSON_ENABLE_THREADS=ON # This should be the default. Testing anyway.
CTEST_ARGS: -E checkperf
build_script:
- set
- mkdir build
- cd build
- cmake --version
- cmake -DSIMDJSON_BUILD_STATIC=%SIMDJSON_BUILD_STATIC% -DSIMDJSON_ENABLE_THREADS=%SIMDJSON_ENABLE_THREADS% -DCMAKE_BUILD_TYPE=%Configuration% -DCMAKE_GENERATOR_PLATFORM=%SIMDJSON_PLATFORM% ..
- cmake -A %Platform% %CMAKE_ARGS% --parallel ..
- cmake -LH ..
- cmake --build . --config %Configuration% --verbose --parallel
test_script:
- ctest --output-on-failure -C %Configuration% --verbose --parallel
- ctest --output-on-failure -C %Configuration% --verbose %CTEST_ARGS% --parallel
clone_folder: c:\projects\simdjson
matrix:
fast_finish: true
exclude:
# Don't build all variants on 2017, just running it to make sure readme_tests succeed
- image: Visual Studio 2017
SIMDJSON_BUILD_STATIC: ON
SIMDJSON_ENABLE_THREADS: OFF

View File

@ -1,5 +1,5 @@
include_directories( . linux )
link_libraries(simdjson simdjson-flags)
link_libraries(simdjson simdjson-flags simdjson-windows-headers)
# add_executable(benchfeatures benchfeatures.cpp) # doesn't presently compile at all
add_executable(get_corpus_benchmark get_corpus_benchmark.cpp)
add_executable(perfdiff perfdiff.cpp)
@ -35,15 +35,17 @@ if (SIMDJSON_COMPETITION)
target_compile_definitions(allparsingcompetition PRIVATE ALLPARSER)
endif()
IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
add_test(NAME checkperf
COMMAND ${CMAKE_COMMAND} -E env
CHECKPERF_REPOSITORY=https://github.com/simdjson/simdjson
CHECKPERF_BRANCH=master
CHECKPERF_DIR=${CMAKE_CURRENT_BINARY_DIR}/simdjson-master
CHECKPERF_CMAKECACHE=${SIMDJSON_USER_CMAKECACHE}
bash ${CMAKE_CURRENT_SOURCE_DIR}/checkperf.sh ${PROJECT_SOURCE_DIR}/jsonexamples/twitter.json)
set_property(TEST checkperf APPEND PROPERTY LABELS per_implementation)
set_property(TEST checkperf APPEND PROPERTY DEPENDS parse perfdiff ${SIMDJSON_USER_CMAKECACHE})
set_property(TEST checkperf PROPERTY RUN_SERIAL TRUE)
ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
include(checkperf.cmake)
# IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
# add_test(NAME checkperf
# COMMAND ${CMAKE_COMMAND} -E env
# CHECKPERF_REPOSITORY=${SIMDJSON_GITHUB_REPOSITORY}
# CHECKPERF_BRANCH=master
# CHECKPERF_DIR=${CMAKE_CURRENT_BINARY_DIR}/simdjson-master
# CHECKPERF_CMAKECACHE=${SIMDJSON_USER_CMAKECACHE}
# bash ${CMAKE_CURRENT_SOURCE_DIR}/checkperf.sh ${PROJECT_SOURCE_DIR}/jsonexamples/twitter.json)
# set_property(TEST checkperf APPEND PROPERTY LABELS per_implementation)
# set_property(TEST checkperf APPEND PROPERTY DEPENDS parse perfdiff ${SIMDJSON_USER_CMAKECACHE})
# set_property(TEST checkperf PROPERTY RUN_SERIAL TRUE)
# ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")

View File

@ -4,8 +4,8 @@
#include <cctype>
#ifndef _MSC_VER
#include <dirent.h>
#include <unistd.h>
#endif
#include <unistd.h>
#include <cinttypes>
#include <initializer_list>
@ -84,7 +84,6 @@ struct option_struct {
bool verbose = false;
option_struct(int argc, char **argv) {
#ifndef _MSC_VER
int c;
while ((c = getopt(argc, argv, "vtn:i:a:s:")) != -1) {
@ -117,9 +116,6 @@ struct option_struct {
exit_error("Unexpected argument " + c);
}
}
#else
int optind = 1;
#endif
// If architecture is not specified, pick the best supported architecture by default
if (arch == architecture::UNSUPPORTED) {

View File

@ -7,8 +7,8 @@
#include <cctype>
#ifndef _MSC_VER
#include <dirent.h>
#include <unistd.h>
#endif
#include <unistd.h>
#include <cinttypes>
#include <cstdio>
@ -45,6 +45,8 @@ using std::vector;
using std::ostream;
using std::ofstream;
using std::exception;
using std::min;
using std::max;
// Initialize "verbose" to go nowhere. We'll read options in main() and set to cout if verbose is true.
std::ofstream dev_null;
@ -498,8 +500,8 @@ struct benchmarker {
double freq1 = (stage1.best.cycles() / stage1.best.elapsed_sec()) / 1000000000.0;
double freq2 = (stage2.best.cycles() / stage2.best.elapsed_sec()) / 1000000000.0;
double freqall = (all_stages.best.cycles() / all_stages.best.elapsed_sec()) / 1000000000.0;
double freqmin = std::min(freq1, freq2);
double freqmax = std::max(freq1, freq2);
double freqmin = min(freq1, freq2);
double freqmax = max(freq1, freq2);
if((freqall < 0.95 * freqmin) or (freqall > 1.05 * freqmax)) {
printf("\nWarning: The processor frequency fluctuates in an expected way!!!\n"
"Expect the overall speed not to match stage 1 and stage 2 speeds.\n"

93
benchmark/checkperf.cmake Normal file
View File

@ -0,0 +1,93 @@
# Relevant targets:
# checkperf-parse: builds the reference checkperf-parse, syncing reference repository if needed
# checkperf: builds the targets needed for checkperf (parse, perfdiff, checkperf-parse)
# update-checkperf-repo: updates the reference repository we're checking performance against
# checkperf-repo: initialize and sync reference repository (first time only)
# TEST checkperf: runs the actual checkperf test
# Clone the repository if it's not there
find_package(Git QUIET)
if (Git_FOUND)
# sync_git_repository(myrepo ...) creates two targets:
# myrepo - if the repo does not exist, creates and syncs it against the origin branch
# update_myrepo - will update the repo against the origin branch (and create if needed)
function(sync_git_repository name dir remote branch url)
# This conditionally creates the git repository
add_custom_command(
OUTPUT ${dir}/.git/config
COMMAND ${GIT_EXECUTABLE} init ${dir}
COMMAND ${GIT_EXECUTABLE} -C ${dir} remote add ${remote} ${url}
)
add_custom_target(init-${name} DEPENDS ${dir}/.git/config)
# This conditionally syncs the git repository, first time only
add_custom_command(
OUTPUT ${dir}/.git/FETCH_HEAD
COMMAND ${GIT_EXECUTABLE} remote set-url ${remote} ${url}
COMMAND ${GIT_EXECUTABLE} fetch --depth=1 ${remote} ${branch}
COMMAND ${GIT_EXECUTABLE} reset --hard ${remote}/${branch}
WORKING_DIRECTORY ${dir}
DEPENDS init-${name}
)
# This is the ${name} target, which will create and sync the repo first time only
add_custom_target(${name} DEPENDS ${dir}/.git/FETCH_HEAD)
# This is the update-${name} target, which will sync the repo (creating it if needed)
add_custom_target(
update-${name}
COMMAND ${GIT_EXECUTABLE} remote set-url ${remote} ${url}
COMMAND ${GIT_EXECUTABLE} fetch --depth=1 ${remote} ${branch}
COMMAND ${GIT_EXECUTABLE} reset --hard ${remote}/${branch}
WORKING_DIRECTORY ${dir}
DEPENDS init-${name}
)
endfunction(sync_git_repository)
set(SIMDJSON_CHECKPERF_REMOTE origin CACHE STRING "Remote repository to compare performance against")
set(SIMDJSON_CHECKPERF_BRANCH master CACHE STRING "Branch to compare performance against")
set(SIMDJSON_CHECKPERF_DIR ${CMAKE_CURRENT_BINARY_DIR}/checkperf-reference/${SIMDJSON_CHECKPERF_BRANCH} CACHE STRING "Location to put checkperf performance comparison repository")
set(SIMDJSON_CHECKPERF_ARGS ${EXAMPLE_JSON} CACHE STRING "Arguments to pass to parse during checkperf")
sync_git_repository(checkperf-repo ${SIMDJSON_CHECKPERF_DIR} ${SIMDJSON_CHECKPERF_REMOTE} ${SIMDJSON_CHECKPERF_BRANCH} ${SIMDJSON_GITHUB_REPOSITORY})
# Commands to cause cmake on benchmark/checkperf-master/build/
# - first, copy CMakeCache.txt
add_custom_command(
OUTPUT ${SIMDJSON_CHECKPERF_DIR}/build/CMakeCache.txt
COMMAND ${CMAKE_COMMAND} -E make_directory ${SIMDJSON_CHECKPERF_DIR}/build
COMMAND ${CMAKE_COMMAND} -E copy ${SIMDJSON_USER_CMAKECACHE} ${SIMDJSON_CHECKPERF_DIR}/build/CMakeCache.txt
DEPENDS checkperf-repo simdjson-user-cmakecache
)
# - second, cmake ..
add_custom_command(
OUTPUT ${SIMDJSON_CHECKPERF_DIR}/build/cmake_install.cmake # We make many things but this seems the most cross-platform one we can depend on
COMMAND ${CMAKE_COMMAND} -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_COMPETITION=OFF -G ${CMAKE_GENERATOR} ..
WORKING_DIRECTORY ${SIMDJSON_CHECKPERF_DIR}/build
DEPENDS ${SIMDJSON_CHECKPERF_DIR}/build/CMakeCache.txt
)
# - third, build parse.
if (CMAKE_CONFIGURATION_TYPES)
set(CHECKPERF_PARSE ${SIMDJSON_CHECKPERF_DIR}/build/benchmark/$<CONFIGURATION>/parse)
else()
set(CHECKPERF_PARSE ${SIMDJSON_CHECKPERF_DIR}/build/benchmark/parse)
endif()
add_custom_target(
checkperf-parse ALL # TODO is ALL necessary?
# Build parse
COMMAND ${CMAKE_COMMAND} --build . --target parse --config $<CONFIGURATION>
WORKING_DIRECTORY ${SIMDJSON_CHECKPERF_DIR}/build
DEPENDS ${SIMDJSON_CHECKPERF_DIR}/build/cmake_install.cmake # We make many things but this seems the most cross-platform one we can depend on
)
# Target to build everything needed for the checkperf test
add_custom_target(checkperf DEPENDS parse perfdiff checkperf-parse)
# Add the actual checkperf test
add_test(
NAME checkperf
# COMMAND ECHO $<TARGET_FILE:perfdiff> \"$<TARGET_FILE:parse> -t ${SIMDJSON_CHECKPERF_ARGS}\" \"${CHECKPERF_PARSE} -t ${SIMDJSON_CHECKPERF_ARGS}\" }
COMMAND $<TARGET_FILE:perfdiff> $<TARGET_FILE:parse> ${CHECKPERF_PARSE} -t ${SIMDJSON_CHECKPERF_ARGS}
)
set_property(TEST checkperf APPEND PROPERTY LABELS per_implementation)
set_property(TEST checkperf APPEND PROPERTY DEPENDS parse perfdiff ${SIMDJSON_USER_CMAKECACHE})
set_property(TEST checkperf PROPERTY RUN_SERIAL TRUE)
endif (Git_FOUND)

View File

@ -1,48 +0,0 @@
#!/bin/bash
set -ex
set | grep CHECKPERF
if [ -z "$CHECKPERF_REPOSITORY" ]; then CHECKPERF_REPOSITORY=.; fi
# Arguments: perfdiff.sh <branch> <test json files>
if [ -z "$CHECKPERF_BRANCH" ]; then CHECKPERF_BRANCH=master; fi
if [ -z "$CHECKPERF_DIR" ]; then CHECKPERF_DIR=benchbranch/$CHECKPERF_BRANCH; fi
if [ -z "$CHECKPERF_ARGS" ]; then
if [ -z "$*" ]; then
CHECKPERF_ARGS=jsonexamples/twitter.json;
else
CHECKPERF_ARGS=$*;
fi
fi
# Clone and build the reference branch's parse
if [ -d $CHECKPERF_DIR/.git ]; then
echo "Checking out the reference branch ($CHECKPERF_BRANCH) into $CHECKPERF_DIR ..."
pushd $CHECKPERF_DIR
git remote update
git reset --hard origin/$CHECKPERF_BRANCH
else
echo "Cloning the reference branch ($CHECKPERF_BRANCH) into $CHECKPERF_DIR ..."
mkdir -p $CHECKPERF_DIR
git clone --depth 1 -b $CHECKPERF_BRANCH $CHECKPERF_REPOSITORY $CHECKPERF_DIR
pushd $CHECKPERF_DIR
fi
echo "Building $CHECKPERF_DIR/parse ..."
if [ -n "$CHECKPERF_CMAKECACHE" ]; then
cp $CHECKPERF_CMAKECACHE $CHECKPERF_DIR/CMakeCache.txt
cmake -DSIMDJSON_GOOGLE_BENCHMARKS=OFF -DSIMDJSON_COMPETITION=OFF .
make parse
REFERENCE_PARSE=$CHECKPERF_DIR/benchmark/parse
else
make parse
REFERENCE_PARSE=$CHECKPERF_DIR/parse
fi
popd
# Run them and diff performance
echo "Running perfdiff:"
echo ./perfdiff \"./parse -t $CHECKPERF_ARGS\" \"$REFERENCE_PARSE -t $CHECKPERF_ARGS\"
./perfdiff "./parse -t $CHECKPERF_ARGS" "$REFERENCE_PARSE -t $CHECKPERF_ARGS"

View File

@ -5,8 +5,8 @@
#include <cctype>
#ifndef _MSC_VER
#include <dirent.h>
#include <unistd.h>
#endif
#include <unistd.h>
#include <cinttypes>
#include <cstdio>

View File

@ -4,8 +4,8 @@
#include <cctype>
#ifndef _MSC_VER
#include <dirent.h>
#include <unistd.h>
#endif
#include <unistd.h>
#include <cinttypes>
#include <cstdio>
@ -89,7 +89,6 @@ struct option_struct {
bool hotbuffers = false;
option_struct(int argc, char **argv) {
#ifndef _MSC_VER
int c;
while ((c = getopt(argc, argv, "vtn:i:a:s:H")) != -1) {
@ -138,9 +137,6 @@ struct option_struct {
" (programming error)");
}
}
#else
int optind = 1;
#endif
// All remaining arguments are considered to be files
for (int i=optind; i<argc; i++) {
@ -154,12 +150,6 @@ struct option_struct {
if (files.size() == 1) {
iteration_step = iterations;
}
#if !defined(__linux__)
if (tabbed_output) {
exit_error("tabbed_output (-t) flag only works under linux.\n");
}
#endif
}
};

View File

@ -1,8 +1,8 @@
#include "simdjson.h"
#include <unistd.h>
#ifndef _MSC_VER
#include "linux-perf-events.h"
#include <unistd.h>
#ifdef __linux__
#include <libgen.h>
#endif //__linux__

View File

@ -64,23 +64,33 @@ double readThroughput(std::string parseOutput) {
const double INTERLEAVED_ATTEMPTS = 7;
int main(int argc, char *argv[]) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <new parse cmd> <reference parse cmd>" << std::endl;
int main(int argc, const char *argv[]) {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " <old parse exe> <new parse exe> [<parse arguments>]" << std::endl;
return 1;
}
std::string newCommand = argv[1];
std::string refCommand = argv[2];
for (int i=3; i<argc; i++) {
newCommand += " ";
newCommand += argv[i];
refCommand += " ";
refCommand += argv[i];
}
std::vector<double> ref;
std::vector<double> newcode;
for (int attempt=0; attempt < INTERLEAVED_ATTEMPTS; attempt++) {
std::cout << "Attempt #" << (attempt+1) << " of " << INTERLEAVED_ATTEMPTS << std::endl;
// Read new throughput
double newThroughput = readThroughput(exec(argv[1]));
double newThroughput = readThroughput(exec(newCommand.c_str()));
std::cout << "New throughput: " << newThroughput << std::endl;
newcode.push_back(newThroughput);
// Read reference throughput
double referenceThroughput = readThroughput(exec(argv[2]));
double referenceThroughput = readThroughput(exec(refCommand.c_str()));
std::cout << "Ref throughput: " << referenceThroughput << std::endl;
ref.push_back(referenceThroughput);
}

View File

@ -1,7 +1,5 @@
#include <iostream>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include "simdjson.h"
#ifdef __linux__
#include "linux-perf-events.h"

View File

@ -5,12 +5,20 @@
# Not supported on Windows at present, because the only thing that uses it is checkperf, which we
# don't run on Windows.
#
if (NOT MSVC)
set(SIMDJSON_USER_CMAKECACHE ${CMAKE_CURRENT_BINARY_DIR}/.simdjson-user-CMakeCache.txt)
set(SIMDJSON_USER_CMAKECACHE ${CMAKE_CURRENT_BINARY_DIR}/.simdjson-user-CMakeCache.txt)
if (MSVC)
add_custom_command(
OUTPUT ${SIMDJSON_USER_CMAKECACHE}
COMMAND bash -c "grep SIMDJSON_ ${PROJECT_BINARY_DIR}/CMakeCache.txt | grep -v SIMDJSON_LIB_ > ${SIMDJSON_USER_CMAKECACHE}"
COMMAND findstr SIMDJSON_ ${PROJECT_BINARY_DIR}/CMakeCache.txt > ${SIMDJSON_USER_CMAKECACHE}.tmp
COMMAND findstr /v SIMDJSON_LIB_ ${SIMDJSON_USER_CMAKECACHE}.tmp > ${SIMDJSON_USER_CMAKECACHE}
VERBATIM # Makes it not do weird escaping with the command
)
else()
add_custom_command(
OUTPUT ${SIMDJSON_USER_CMAKECACHE}
COMMAND grep SIMDJSON_ ${PROJECT_BINARY_DIR}/CMakeCache.txt > ${SIMDJSON_USER_CMAKECACHE}.tmp
COMMAND grep -v SIMDJSON_LIB_ ${SIMDJSON_USER_CMAKECACHE}.tmp > ${SIMDJSON_USER_CMAKECACHE}
VERBATIM # Makes it not do weird escaping with the command
)
add_custom_target(simdjson-user-cmakecache ALL DEPENDS ${SIMDJSON_USER_CMAKECACHE})
endif()
add_custom_target(simdjson-user-cmakecache DEPENDS ${SIMDJSON_USER_CMAKECACHE})

View File

@ -11,9 +11,7 @@
#include <sstream>
#include <utility>
#include <ciso646>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include "simdjson.h"
@ -1905,7 +1903,6 @@ namespace format_tests {
int main(int argc, char *argv[]) {
std::cout << std::unitbuf;
#ifndef _MSC_VER
int c;
while ((c = getopt(argc, argv, "a:")) != -1) {
switch (c) {
@ -1923,9 +1920,6 @@ int main(int argc, char *argv[]) {
return EXIT_FAILURE;
}
}
#else
int optind = 1;
#endif
// this is put here deliberately to check that the documentation is correct (README),
// should this fail to compile, you should update the documentation:

View File

@ -1,11 +1,11 @@
#include <cstring>
#ifndef _MSC_VER
#include <dirent.h>
#include <unistd.h>
#else
// Microsoft can't be bothered to provide standard utils.
#include <dirent_portable.h>
#endif
#include <unistd.h>
#include <cinttypes>
#include <cstdio>
@ -112,7 +112,6 @@ bool validate(const char *dirname) {
}
int main(int argc, char *argv[]) {
#ifndef _MSC_VER
int c;
while ((c = getopt(argc, argv, "a:")) != -1) {
switch (c) {
@ -130,9 +129,6 @@ int main(int argc, char *argv[]) {
return EXIT_FAILURE;
}
}
#else
int optind = 1;
#endif
if ((argc - optind) != 1) {
std::cerr << "Usage: " << argv[0] << " <directorywithjsonfiles>"
<< std::endl;

View File

@ -1,11 +1,11 @@
#include <cstring>
#ifndef _MSC_VER
#include <dirent.h>
#include <unistd.h>
#else
// Microsoft can't be bothered to provide standard utils.
#include <dirent_portable.h>
#endif
#include <unistd.h>
#include <cinttypes>
#include <cstdio>

View File

@ -1,7 +1,5 @@
#include <iostream>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include "simdjson.h"
void usage(const char *exe) {
@ -14,7 +12,6 @@ void usage(const char *exe) {
int main(int argc, char *argv[]) {
bool rawdump = false;
#ifndef _MSC_VER
int c;
while ((c = getopt(argc, argv, "dh")) != -1) {
@ -29,9 +26,6 @@ int main(int argc, char *argv[]) {
abort();
}
}
#else
int optind = 1;
#endif
if (optind >= argc) {
usage(argv[0]);
return EXIT_FAILURE;

View File

@ -1,8 +1,8 @@
#include <iostream>
#ifndef _MSC_VER
#include <dirent.h>
#include <unistd.h>
#endif
#include <unistd.h>
#include "simdjson.h"
@ -35,7 +35,6 @@ struct option_struct {
char* filename{};
option_struct(int argc, char **argv) {
#ifndef _MSC_VER
int c;
while ((c = getopt(argc, argv, "a:")) != -1) {
@ -60,9 +59,6 @@ struct option_struct {
" (programming error)");
}
}
#else
int optind = 1;
#endif
// All remaining arguments are considered to be files
if(optind + 1 == argc) {

654
windows/getopt.h Normal file
View File

@ -0,0 +1,654 @@
#ifndef __GETOPT_H__
/* From https://github.com/skandhurkat/Getopt-for-Visual-Studio/blob/master/getopt.h */
/**
* DISCLAIMER
* This file is part of the mingw-w64 runtime package.
*
* The mingw-w64 runtime package and its code is distributed in the hope that it
* will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR
* IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to
* warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
/*
* Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Sponsored in part by the Defense Advanced Research Projects
* Agency (DARPA) and Air Force Research Laboratory, Air Force
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
*/
/*-
* Copyright (c) 2000 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Dieter Baron and Thomas Klausner.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#pragma warning(disable:4996)
#define __GETOPT_H__
/* All the headers include this file. */
#include <crtdefs.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif
#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */
#ifdef REPLACE_GETOPT
int opterr = 1; /* if error message should be printed */
int optind = 1; /* index into parent argv vector */
int optopt = '?'; /* character checked for validity */
#undef optreset /* see getopt.h */
#define optreset __mingw_optreset
int optreset; /* reset getopt */
char *optarg; /* argument associated with option */
#endif
//extern int optind; /* index of first non-option in argv */
//extern int optopt; /* single option character, as parsed */
//extern int opterr; /* flag to enable built-in diagnostics... */
// /* (user may set to zero, to suppress) */
//
//extern char *optarg; /* pointer to argument of current option */
#define PRINT_ERROR ((opterr) && (*options != ':'))
#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */
#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */
#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */
/* return values */
#define BADCH (int)'?'
#define BADARG ((*options == ':') ? (int)':' : (int)'?')
#define INORDER (int)1
#ifndef __CYGWIN__
#define __progname __argv[0]
#else
extern char __declspec(dllimport) *__progname;
#endif
#ifdef __CYGWIN__
static char EMSG[] = "";
#else
#define EMSG ""
#endif
static int getopt_internal(int, char * const *, const char *,
const struct option *, int *, int);
static int parse_long_options(char * const *, const char *,
const struct option *, int *, int);
static int gcd(int, int);
static void permute_args(int, int, int, char * const *);
static char *place = EMSG; /* option letter processing */
/* XXX: set optreset to 1 rather than these two */
static int nonopt_start = -1; /* first non option argument (for permute) */
static int nonopt_end = -1; /* first option after non options (for permute) */
/* Error messages */
static const char recargchar[] = "option requires an argument -- %c";
static const char recargstring[] = "option requires an argument -- %s";
static const char ambig[] = "ambiguous option -- %.*s";
static const char noarg[] = "option doesn't take an argument -- %.*s";
static const char illoptchar[] = "unknown option -- %c";
static const char illoptstring[] = "unknown option -- %s";
static void
_vwarnx(const char *fmt,va_list ap)
{
(void)fprintf(stderr,"%s: ",__progname);
if (fmt != NULL)
(void)vfprintf(stderr,fmt,ap);
(void)fprintf(stderr,"\n");
}
static void
warnx(const char *fmt,...)
{
va_list ap;
va_start(ap,fmt);
_vwarnx(fmt,ap);
va_end(ap);
}
/*
* Compute the greatest common divisor of a and b.
*/
static int
gcd(int a, int b)
{
int c;
c = a % b;
while (c != 0) {
a = b;
b = c;
c = a % b;
}
return (b);
}
/*
* Exchange the block from nonopt_start to nonopt_end with the block
* from nonopt_end to opt_end (keeping the same order of arguments
* in each block).
*/
static void
permute_args(int panonopt_start, int panonopt_end, int opt_end,
char * const *nargv)
{
int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
char *swap;
/*
* compute lengths of blocks and number and size of cycles
*/
nnonopts = panonopt_end - panonopt_start;
nopts = opt_end - panonopt_end;
ncycle = gcd(nnonopts, nopts);
cyclelen = (opt_end - panonopt_start) / ncycle;
for (i = 0; i < ncycle; i++) {
cstart = panonopt_end+i;
pos = cstart;
for (j = 0; j < cyclelen; j++) {
if (pos >= panonopt_end)
pos -= nnonopts;
else
pos += nopts;
swap = nargv[pos];
/* LINTED const cast */
((char **) nargv)[pos] = nargv[cstart];
/* LINTED const cast */
((char **)nargv)[cstart] = swap;
}
}
}
#ifdef REPLACE_GETOPT
/*
* getopt --
* Parse argc/argv argument vector.
*
* [eventually this will replace the BSD getopt]
*/
int
getopt(int nargc, char * const *nargv, const char *options)
{
/*
* We don't pass FLAG_PERMUTE to getopt_internal() since
* the BSD getopt(3) (unlike GNU) has never done this.
*
* Furthermore, since many privileged programs call getopt()
* before dropping privileges it makes sense to keep things
* as simple (and bug-free) as possible.
*/
return (getopt_internal(nargc, nargv, options, NULL, NULL, 0));
}
#endif /* REPLACE_GETOPT */
//extern int getopt(int nargc, char * const *nargv, const char *options);
#ifdef _BSD_SOURCE
/*
* BSD adds the non-standard `optreset' feature, for reinitialisation
* of `getopt' parsing. We support this feature, for applications which
* proclaim their BSD heritage, before including this header; however,
* to maintain portability, developers are advised to avoid it.
*/
# define optreset __mingw_optreset
extern int optreset;
#endif
#ifdef __cplusplus
}
#endif
/*
* POSIX requires the `getopt' API to be specified in `unistd.h';
* thus, `unistd.h' includes this header. However, we do not want
* to expose the `getopt_long' or `getopt_long_only' APIs, when
* included in this manner. Thus, close the standard __GETOPT_H__
* declarations block, and open an additional __GETOPT_LONG_H__
* specific block, only when *not* __UNISTD_H_SOURCED__, in which
* to declare the extended API.
*/
#endif /* !defined(__GETOPT_H__) */
#if !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__)
#define __GETOPT_LONG_H__
#ifdef __cplusplus
extern "C" {
#endif
struct option /* specification for a long form option... */
{
const char *name; /* option name, without leading hyphens */
int has_arg; /* does it take an argument? */
int *flag; /* where to save its status, or NULL */
int val; /* its associated status value */
};
enum /* permitted values for its `has_arg' field... */
{
no_argument = 0, /* option never takes an argument */
required_argument, /* option always requires an argument */
optional_argument /* option may take an argument */
};
/*
* parse_long_options --
* Parse long options in argc/argv argument vector.
* Returns -1 if short_too is set and the option does not match long_options.
*/
static int
parse_long_options(char * const *nargv, const char *options,
const struct option *long_options, int *idx, int short_too)
{
char *current_argv, *has_equal;
size_t current_argv_len;
int i, ambiguous, match;
#define IDENTICAL_INTERPRETATION(_x, _y) \
(long_options[(_x)].has_arg == long_options[(_y)].has_arg && \
long_options[(_x)].flag == long_options[(_y)].flag && \
long_options[(_x)].val == long_options[(_y)].val)
current_argv = place;
match = -1;
ambiguous = 0;
optind++;
if ((has_equal = strchr(current_argv, '=')) != NULL) {
/* argument found (--option=arg) */
current_argv_len = has_equal - current_argv;
has_equal++;
} else
current_argv_len = strlen(current_argv);
for (i = 0; long_options[i].name; i++) {
/* find matching long option */
if (strncmp(current_argv, long_options[i].name,
current_argv_len))
continue;
if (strlen(long_options[i].name) == current_argv_len) {
/* exact match */
match = i;
ambiguous = 0;
break;
}
/*
* If this is a known short option, don't allow
* a partial match of a single character.
*/
if (short_too && current_argv_len == 1)
continue;
if (match == -1) /* partial match */
match = i;
else if (!IDENTICAL_INTERPRETATION(i, match))
ambiguous = 1;
}
if (ambiguous) {
/* ambiguous abbreviation */
if (PRINT_ERROR)
warnx(ambig, (int)current_argv_len,
current_argv);
optopt = 0;
return (BADCH);
}
if (match != -1) { /* option found */
if (long_options[match].has_arg == no_argument
&& has_equal) {
if (PRINT_ERROR)
warnx(noarg, (int)current_argv_len,
current_argv);
/*
* XXX: GNU sets optopt to val regardless of flag
*/
if (long_options[match].flag == NULL)
optopt = long_options[match].val;
else
optopt = 0;
return (BADARG);
}
if (long_options[match].has_arg == required_argument ||
long_options[match].has_arg == optional_argument) {
if (has_equal)
optarg = has_equal;
else if (long_options[match].has_arg ==
required_argument) {
/*
* optional argument doesn't use next nargv
*/
optarg = nargv[optind++];
}
}
if ((long_options[match].has_arg == required_argument)
&& (optarg == NULL)) {
/*
* Missing argument; leading ':' indicates no error
* should be generated.
*/
if (PRINT_ERROR)
warnx(recargstring,
current_argv);
/*
* XXX: GNU sets optopt to val regardless of flag
*/
if (long_options[match].flag == NULL)
optopt = long_options[match].val;
else
optopt = 0;
--optind;
return (BADARG);
}
} else { /* unknown option */
if (short_too) {
--optind;
return (-1);
}
if (PRINT_ERROR)
warnx(illoptstring, current_argv);
optopt = 0;
return (BADCH);
}
if (idx)
*idx = match;
if (long_options[match].flag) {
*long_options[match].flag = long_options[match].val;
return (0);
} else
return (long_options[match].val);
#undef IDENTICAL_INTERPRETATION
}
/*
* getopt_internal --
* Parse argc/argv argument vector. Called by user level routines.
*/
static int
getopt_internal(int nargc, char * const *nargv, const char *options,
const struct option *long_options, int *idx, int flags)
{
char *oli; /* option letter list index */
int optchar, short_too;
static int posixly_correct = -1;
if (options == NULL)
return (-1);
/*
* XXX Some GNU programs (like cvs) set optind to 0 instead of
* XXX using optreset. Work around this braindamage.
*/
if (optind == 0)
optind = optreset = 1;
/*
* Disable GNU extensions if POSIXLY_CORRECT is set or options
* string begins with a '+'.
*
* CV, 2009-12-14: Check POSIXLY_CORRECT anew if optind == 0 or
* optreset != 0 for GNU compatibility.
*/
if (posixly_correct == -1 || optreset != 0)
posixly_correct = (getenv("POSIXLY_CORRECT") != NULL);
if (*options == '-')
flags |= FLAG_ALLARGS;
else if (posixly_correct || *options == '+')
flags &= ~FLAG_PERMUTE;
if (*options == '+' || *options == '-')
options++;
optarg = NULL;
if (optreset)
nonopt_start = nonopt_end = -1;
start:
if (optreset || !*place) { /* update scanning pointer */
optreset = 0;
if (optind >= nargc) { /* end of argument vector */
place = EMSG;
if (nonopt_end != -1) {
/* do permutation, if we have to */
permute_args(nonopt_start, nonopt_end,
optind, nargv);
optind -= nonopt_end - nonopt_start;
}
else if (nonopt_start != -1) {
/*
* If we skipped non-options, set optind
* to the first of them.
*/
optind = nonopt_start;
}
nonopt_start = nonopt_end = -1;
return (-1);
}
if (*(place = nargv[optind]) != '-' ||
(place[1] == '\0' && strchr(options, '-') == NULL)) {
place = EMSG; /* found non-option */
if (flags & FLAG_ALLARGS) {
/*
* GNU extension:
* return non-option as argument to option 1
*/
optarg = nargv[optind++];
return (INORDER);
}
if (!(flags & FLAG_PERMUTE)) {
/*
* If no permutation wanted, stop parsing
* at first non-option.
*/
return (-1);
}
/* do permutation */
if (nonopt_start == -1)
nonopt_start = optind;
else if (nonopt_end != -1) {
permute_args(nonopt_start, nonopt_end,
optind, nargv);
nonopt_start = optind -
(nonopt_end - nonopt_start);
nonopt_end = -1;
}
optind++;
/* process next argument */
goto start;
}
if (nonopt_start != -1 && nonopt_end == -1)
nonopt_end = optind;
/*
* If we have "-" do nothing, if "--" we are done.
*/
if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
optind++;
place = EMSG;
/*
* We found an option (--), so if we skipped
* non-options, we have to permute.
*/
if (nonopt_end != -1) {
permute_args(nonopt_start, nonopt_end,
optind, nargv);
optind -= nonopt_end - nonopt_start;
}
nonopt_start = nonopt_end = -1;
return (-1);
}
}
/*
* Check long options if:
* 1) we were passed some
* 2) the arg is not just "-"
* 3) either the arg starts with -- we are getopt_long_only()
*/
if (long_options != NULL && place != nargv[optind] &&
(*place == '-' || (flags & FLAG_LONGONLY))) {
short_too = 0;
if (*place == '-')
place++; /* --foo long option */
else if (*place != ':' && strchr(options, *place) != NULL)
short_too = 1; /* could be short option too */
optchar = parse_long_options(nargv, options, long_options,
idx, short_too);
if (optchar != -1) {
place = EMSG;
return (optchar);
}
}
if ((optchar = (int)*place++) == (int)':' ||
(optchar == (int)'-' && *place != '\0') ||
(oli = (char*)strchr(options, optchar)) == NULL) {
/*
* If the user specified "-" and '-' isn't listed in
* options, return -1 (non-option) as per POSIX.
* Otherwise, it is an unknown option character (or ':').
*/
if (optchar == (int)'-' && *place == '\0')
return (-1);
if (!*place)
++optind;
if (PRINT_ERROR)
warnx(illoptchar, optchar);
optopt = optchar;
return (BADCH);
}
if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
/* -W long-option */
if (*place) /* no space */
/* NOTHING */;
else if (++optind >= nargc) { /* no arg */
place = EMSG;
if (PRINT_ERROR)
warnx(recargchar, optchar);
optopt = optchar;
return (BADARG);
} else /* white space */
place = nargv[optind];
optchar = parse_long_options(nargv, options, long_options,
idx, 0);
place = EMSG;
return (optchar);
}
if (*++oli != ':') { /* doesn't take argument */
if (!*place)
++optind;
} else { /* takes (optional) argument */
optarg = NULL;
if (*place) /* no white space */
optarg = place;
else if (oli[1] != ':') { /* arg not optional */
if (++optind >= nargc) { /* no arg */
place = EMSG;
if (PRINT_ERROR)
warnx(recargchar, optchar);
optopt = optchar;
return (BADARG);
} else
optarg = nargv[optind];
}
place = EMSG;
++optind;
}
/* dump back option letter */
return (optchar);
}
/*
* getopt_long --
* Parse argc/argv argument vector.
*/
int
getopt_long(int nargc, char * const *nargv, const char *options,
const struct option *long_options, int *idx)
{
return (getopt_internal(nargc, nargv, options, long_options, idx,
FLAG_PERMUTE));
}
/*
* getopt_long_only --
* Parse argc/argv argument vector.
*/
int
getopt_long_only(int nargc, char * const *nargv, const char *options,
const struct option *long_options, int *idx)
{
return (getopt_internal(nargc, nargv, options, long_options, idx,
FLAG_PERMUTE|FLAG_LONGONLY));
}
//extern int getopt_long(int nargc, char * const *nargv, const char *options,
// const struct option *long_options, int *idx);
//extern int getopt_long_only(int nargc, char * const *nargv, const char *options,
// const struct option *long_options, int *idx);
/*
* Previous MinGW implementation had...
*/
#ifndef HAVE_DECL_GETOPT
/*
* ...for the long form API only; keep this for compatibility.
*/
# define HAVE_DECL_GETOPT 1
#endif
#ifdef __cplusplus
}
#endif
#endif /* !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) */

3
windows/unistd.h Normal file
View File

@ -0,0 +1,3 @@
#ifndef __UNISTD_H__
#include "getopt.h"
#endif // __UNISTD_H__