[clang] rewrite hijack_and_normalize_clang_command in OCaml

Summary:
Let's start migrating some of our bash script to OCaml to make them easier to
maintain and extend.

For now replace just one script and put it in lib/clang_wrappers/ at compile
time, where the former script used to be. Further simplifications will come
later.

Reviewed By: jberdine

Differential Revision: D3929988

fbshipit-source-id: b2d8b37
master
Jules Villard 8 years ago committed by Facebook Github Bot
parent 4a35862aa8
commit 7d49e16d63

@ -1 +0,0 @@
hijack_and_normalize_clang_command.sh

@ -1 +0,0 @@
hijack_and_normalize_clang_command.sh

@ -1,83 +0,0 @@
#!/bin/bash
# Copyright (c) 2016 - present Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
# Given the more-or-less raw arguments passed to clang as arguments,
# this normalizes them via `clang -###` if needed to call the script
# that actually attaches the plugin on each source file. Unless we
# don't want to attach the plugin, in which case just run the original
# command.
#### Configuration ####
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# script to run our own clang
CLANG_COMPILER="${SCRIPT_DIR}/filter_args_and_run_fcp_clang"
# script to attach the plugin to clang -cc1 commands and run InferClang
CLANG_CC1_CAPTURE="${SCRIPT_DIR}/attach_plugin_and_run_clang_frontend.sh"
# path to Apple's clang
APPLE_CLANG="$FCP_APPLE_CLANG"
# Main
if [ "${0%++}" != "$0" ]; then XX="++"; fi
# Skip -cc1as commands
if [ "$1" = "-cc1as" ]; then
STATUS=0
# Normalize clang command if not -cc1 already. -cc1 is always the first argument if present.
elif [ "$1" = "-cc1" ]; then
"$CLANG_CC1_CAPTURE" "$@"
STATUS=$?
else
# Run `clang -###` to get one compile command per source file.
# Slow since it spawns clang as a separate process
#
# Generate a command containing all the commands in the output of `clang -###`. These are
# the lines that start with ' "/absolute/path/to/binary"'.
#
# In that command, replace /absolute/path/to/clang with our own wrapper, but only for the
# core compiler commands (those that start with "-cc1"). This means we'll capture all
# compilation commands (one per source file), without interfering with non-compiler commands
# (as they run with absolute paths, so they won't get captured again further down the line).
#
# Fail on errors: if we detect an error in the output of `clang -###`, we add the line
# `echo <error>; exit 1` to the generated command. This is because `clang -###` pretty much
# never fails, but warns of failures on stderr instead.
CC_COMMAND=$("$CLANG_COMPILER$XX" -### "$@" 2>&1 | \
# only keep lines that are commands or errors
grep -e '^\([[:space:]]\"\|clang: error:\)' | \
# replace -cc1 commands with our clang wrapper
sed -e "s#^[[:space:]]\"\([^\"]*\)\" \"-cc1\" \(.*\)\$# \"$CLANG_CC1_CAPTURE\" \"-cc1\" \2#g" | \
# do not run if language is assembler or assembler-with-cpp
grep -v -- '"-x" "assembler' | \
# do not run -cc1as commands
grep -v -- '"-cc1as"' | \
# replace error messages by failures
sed -e 's#^\(^clang: error:.*$\)#echo "\1"; exit 1#g' | \
# add trailing ; to each line
sed -e 's/$/;/g')
if [ -n "$CC_COMMAND" ]; then
eval $CC_COMMAND
else
# No command to execute after -###, this is fishy, let's execute the original command
# instead.
#
# In particular, this can happen when the user tries to run `infer -- clang -c
# file_that_does_not_exist.c`. In this case, this will fail with the appropriate error
# message from clang instead of silently analyzing 0 files.
"$CLANG_COMPILER$XX" "$@"
fi
STATUS=$?
fi
# run Apple clang if required (and if any)
if [ -n "$APPLE_CLANG" ]; then
"$APPLE_CLANG$XX" "$@" || exit $?
fi
exit $STATUS

@ -10,9 +10,11 @@ if [ -z "$INFER_RESULTS_DIR" ]; then
fi fi
# invoke the right compiler looking at the final plusplus (e.g. gcc/g++ clang/clang++) # invoke the right compiler looking at the final plusplus (e.g. gcc/g++ clang/clang++)
if [ "${0%++}" != "$0" ]; then XX="++"; fi if [ "${0%++}" != "$0" ]; then INFER_XX="++"; fi
FRONTEND_COMMAND=("$SCRIPT_DIR/../clang_wrappers/hijack_and_normalize_clang_command$XX" "$@") export INFER_XX
HOST_COMPILER_COMMAND=("$SCRIPT_DIR/../clang_wrappers/filter_args_and_run_fcp_clang$XX" "$@")
FRONTEND_COMMAND=("$SCRIPT_DIR/../clang_wrappers/InferClangWrapper" "$@")
HOST_COMPILER_COMMAND=("$SCRIPT_DIR/../clang_wrappers/filter_args_and_run_fcp_clang$INFER_XX" "$@")
if [ -n "$INFER_COMPILER_WRAPPER_IN_RECURSION" ]; then if [ -n "$INFER_COMPILER_WRAPPER_IN_RECURSION" ]; then
if [ -z "$INFER_LISTENER" ]; then if [ -z "$INFER_LISTENER" ]; then

@ -3,9 +3,10 @@
SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
CLANG_WRAPPERS_PATH="${SCRIPT_PATH}/../clang_wrappers" CLANG_WRAPPERS_PATH="${SCRIPT_PATH}/../clang_wrappers"
if [ "${0%++}" != "$0" ]; then XX="++"; else XX=""; fi if [ "${0%++}" != "$0" ]; then INFER_XX="++"; fi
export INFER_XX
export FCP_CLANG_COMPILER="${CLANG_WRAPPERS_PATH%/}/filter_args_and_run_fcp_clang$XX"; export FCP_CLANG_COMPILER="${CLANG_WRAPPERS_PATH%/}/filter_args_and_run_fcp_clang$INFER_XX";
export FCP_RESULTS_DIR="${INFER_RESULTS_DIR}"; export FCP_RESULTS_DIR="${INFER_RESULTS_DIR}";
if [ -z "$INFER_RESULTS_DIR" ]; then if [ -z "$INFER_RESULTS_DIR" ]; then
@ -16,4 +17,4 @@ if [ -z "$INFER_RESULTS_DIR" ]; then
exit $? exit $?
fi fi
"${CLANG_WRAPPERS_PATH%/}/hijack_and_normalize_clang_command$XX" "$@" -fno-cxx-modules "${CLANG_WRAPPERS_PATH%/}/InferClangWrapper" "$@" -fno-cxx-modules

@ -122,6 +122,9 @@ SCRIPT_SOURCES = scripts
CHECKCOPYRIGHT_BIN = $(SCRIPT_DIR)/checkCopyright CHECKCOPYRIGHT_BIN = $(SCRIPT_DIR)/checkCopyright
CHECKCOPYRIGHT_MAIN = $(SCRIPT_SOURCES)/checkCopyright CHECKCOPYRIGHT_MAIN = $(SCRIPT_SOURCES)/checkCopyright
CLANGWRAPPER_BIN = $(LIB_DIR)/clang_wrappers/InferClangWrapper
CLANGWRAPPER_MAIN = $(SCRIPT_SOURCES)/InferClangWrapper
STATSAGGREGATOR_BIN = $(BIN_DIR)/InferStatsAggregator STATSAGGREGATOR_BIN = $(BIN_DIR)/InferStatsAggregator
STATSAGGREGATOR_MAIN = $(SCRIPT_SOURCES)/StatsAggregator STATSAGGREGATOR_MAIN = $(SCRIPT_SOURCES)/StatsAggregator
@ -154,8 +157,9 @@ INFER_BASE_TARGETS = \
INFER_ALL_TARGETS = $(INFER_BASE_TARGETS) \ INFER_ALL_TARGETS = $(INFER_BASE_TARGETS) \
$(INFERJAVA_MAIN).native \ $(INFERJAVA_MAIN).native \
$(INFERCLANG_MAIN).native \ $(INFERCLANG_MAIN).native \
$(CLANGWRAPPER_MAIN).native \
$(INFERLLVM_MAIN).native \ $(INFERLLVM_MAIN).native \
$(BUCK_COMPILATION_DATABASE_MAIN).native $(BUCK_COMPILATION_DATABASE_MAIN).native
# configure-aware ocamlbuild commands and targets # configure-aware ocamlbuild commands and targets
OCAMLBUILD_CONFIG = $(OCAMLBUILD_BASE) OCAMLBUILD_CONFIG = $(OCAMLBUILD_BASE)
@ -167,7 +171,7 @@ INFER_CONFIG_TARGETS += $(INFERJAVA_MAIN).native
DEPENDENCIES += java DEPENDENCIES += java
endif endif
ifeq ($(BUILD_C_ANALYZERS),yes) ifeq ($(BUILD_C_ANALYZERS),yes)
INFER_CONFIG_TARGETS += $(INFERCLANG_MAIN).native INFER_CONFIG_TARGETS += $(INFERCLANG_MAIN).native $(CLANGWRAPPER_MAIN).native
INFER_CONFIG_TARGETS += $(BUCK_COMPILATION_DATABASE_MAIN).native INFER_CONFIG_TARGETS += $(BUCK_COMPILATION_DATABASE_MAIN).native
DEPENDENCIES += clang DEPENDENCIES += clang
endif endif
@ -196,6 +200,7 @@ ifeq ($(BUILD_JAVA_ANALYZERS),yes)
endif endif
ifeq ($(BUILD_C_ANALYZERS),yes) ifeq ($(BUILD_C_ANALYZERS),yes)
$(COPY) $(INFER_BUILD_DIR)/$(INFERCLANG_MAIN).native $(INFERCLANG_BIN) $(COPY) $(INFER_BUILD_DIR)/$(INFERCLANG_MAIN).native $(INFERCLANG_BIN)
$(COPY) $(INFER_BUILD_DIR)/$(CLANGWRAPPER_MAIN).native $(CLANGWRAPPER_BIN)
$(COPY) $(INFER_BUILD_DIR)/$(BUCK_COMPILATION_DATABASE_MAIN).native $(INFER_BUCK_COMPILATION_DATABASE_BIN) $(COPY) $(INFER_BUILD_DIR)/$(BUCK_COMPILATION_DATABASE_MAIN).native $(INFER_BUCK_COMPILATION_DATABASE_BIN)
endif endif
ifeq ($(ENABLE_OCAML_ANNOT),yes) ifeq ($(ENABLE_OCAML_ANNOT),yes)
@ -368,7 +373,7 @@ endif
$(REMOVE) backend/jsonbug_{j,t}.ml{,i} $(REMOVE) backend/jsonbug_{j,t}.ml{,i}
$(REMOVE) checkers/stacktree_{j,t}.ml{,i} $(REMOVE) checkers/stacktree_{j,t}.ml{,i}
$(REMOVE) $(INFER_BIN) $(INFERANALYZE_BIN) $(INFERPRINT_BIN) $(STATSAGGREGATOR_BIN) $(REMOVE) $(INFER_BIN) $(INFERANALYZE_BIN) $(INFERPRINT_BIN) $(STATSAGGREGATOR_BIN)
$(REMOVE) $(INFERJAVA_BIN) $(INFERCLANG_BIN) $(INFERLLVM_BIN) $(REMOVE) $(INFERJAVA_BIN) $(INFERCLANG_BIN) $(CLANGWRAPPER_BIN) $(INFERLLVM_BIN)
$(REMOVE) $(INFERUNIT_BIN) $(CHECKCOPYRIGHT_BIN) $(INFER_BUCK_COMPILATION_DATABASE_BIN) $(REMOVE) $(INFERUNIT_BIN) $(CHECKCOPYRIGHT_BIN) $(INFER_BUCK_COMPILATION_DATABASE_BIN)
$(REMOVE) $(CLANG_ATDGEN_STUBS) $(REMOVE) $(CLANG_ATDGEN_STUBS)
$(REMOVE) $(INFER_CLANG_FCP_MIRRORED_FILES) $(REMOVE) $(INFER_CLANG_FCP_MIRRORED_FILES)

@ -31,9 +31,9 @@ let exec_command cmd args env =
with (Unix.Unix_error _ as e) -> with (Unix.Unix_error _ as e) ->
print_unix_error cmd e print_unix_error cmd e
(** Given an command to be executed, creates a process to execute this command, (** Given a command to be executed, create a process to execute this command, and wait for it to
and waits for it to terminate. The standard out and error are not redirected. terminate. The standard out and error are not redirected. If the command fails to execute,
If the commands fails to execute, prints an error message and exits. *) print an error message and exit. *)
let create_process_and_wait cmd = let create_process_and_wait cmd =
let pid = Unix.create_process cmd.(0) cmd Unix.stdin Unix.stdout Unix.stderr in let pid = Unix.create_process cmd.(0) cmd Unix.stdin Unix.stdout Unix.stderr in
let _, status = Unix.waitpid [] pid in let _, status = Unix.waitpid [] pid in

@ -17,14 +17,15 @@ module YBU = Yojson.Basic.Util
(** Each command line option may appear in the --help list of any executable, these tags are used to (** Each command line option may appear in the --help list of any executable, these tags are used to
specify which executables for which an option will be documented. *) specify which executables for which an option will be documented. *)
type exe = Analyze | BuckCompilationDatabase | Clang | Interactive | Java | Llvm | Print | type exe = Analyze | BuckCompilationDatabase | Clang | ClangWrapper | Interactive | Java | Llvm
StatsAggregator | Toplevel | Print | StatsAggregator | Toplevel
let exes = [ let exes = [
("InferBuckCompilationDatabase", BuckCompilationDatabase); ("InferBuckCompilationDatabase", BuckCompilationDatabase);
("InferAnalyze", Analyze); ("InferAnalyze", Analyze);
("InferClang", Clang); ("InferClang", Clang);
("InferClangWrapper", ClangWrapper);
("InferJava", Java); ("InferJava", Java);
("InferLLVM", Llvm); ("InferLLVM", Llvm);
("InferPrint", Print); ("InferPrint", Print);
@ -561,9 +562,13 @@ let parse ?(incomplete=false) ?(accept_unknown=false) ?config_file env_var exe_u
let env_args = c_args @ env_args in let env_args = c_args @ env_args in
(* end transitional support for INFERCLANG_ARGS *) (* end transitional support for INFERCLANG_ARGS *)
let exe_name = Sys.executable_name in let exe_name = Sys.executable_name in
let env_cl_args = match current_exe with let should_parse_cl_args = match current_exe with
| Interactive -> env_args | ClangWrapper | Interactive -> false
| _ -> prepend_to_argv env_args in | Analyze | BuckCompilationDatabase | Clang | Java | Llvm | Print | StatsAggregator
| Toplevel -> true in
let env_cl_args =
if should_parse_cl_args then prepend_to_argv env_args
else env_args in
let all_args = match config_file with let all_args = match config_file with
| None -> env_cl_args | None -> env_cl_args
| Some path -> | Some path ->

@ -11,8 +11,8 @@
open! Utils open! Utils
type exe = Analyze | BuckCompilationDatabase | Clang | Interactive | Java | Llvm | Print | type exe = Analyze | BuckCompilationDatabase | Clang | ClangWrapper | Interactive | Java | Llvm
StatsAggregator | Toplevel | Print | StatsAggregator | Toplevel
val current_exe : exe val current_exe : exe

@ -1221,9 +1221,19 @@ let exe_usage (exe : CLOpt.exe) =
Usage: InferAnalyze [options]\n\ Usage: InferAnalyze [options]\n\
Analyze the files captured in the project results directory, \ Analyze the files captured in the project results directory, \
which can be specified with the --results-dir option." which can be specified with the --results-dir option."
| BuckCompilationDatabase ->
"Usage: BuckCompilationDatabase --Xbuck //target \n\
Runs buck with the flavor compilation-database or uber-compilation-database. It then \n\
reads the compilation database emited in json and runs the capture in parallel for \n\
those commands"
| Clang -> | Clang ->
"Usage: InferClang -c <c files> -ast <ast files> --results-dir <output-dir> [options] \n\ "Usage: InferClang -c <c files> -ast <ast files> --results-dir <output-dir> [options] \n\
Translate the given files using clang into infer internal representation for later analysis." Translate the given files using clang into infer internal representation for later analysis."
| ClangWrapper ->
"Usage: internal script to capture compilation commands from clang and clang++. \n\
You shouldn't need to call this directly."
| Interactive ->
"Usage: interactive ocaml toplevel. To pass infer config options use env variable"
| Java -> | Java ->
"Usage: InferJava [options]\n\ "Usage: InferJava [options]\n\
Translate the given files using javac into infer internal representation for later analysis." Translate the given files using javac into infer internal representation for later analysis."
@ -1241,13 +1251,6 @@ let exe_usage (exe : CLOpt.exe) =
Aggregates all the perf stats generated by Buck on each target" Aggregates all the perf stats generated by Buck on each target"
| Toplevel -> | Toplevel ->
version_string version_string
| Interactive ->
"Usage: interactive ocaml toplevel. To pass infer config options use env variable"
| BuckCompilationDatabase ->
"Usage: BuckCompilationDatabase --Xbuck //target \n\
Runs buck with the flavor compilation-database or uber-compilation-database. It then \n\
reads the compilation database emited in json and runs the capture in parallel for \n\
those commands"
let post_parsing_initialization () = let post_parsing_initialization () =
F.set_margin !margin ; F.set_margin !margin ;
@ -1543,6 +1546,7 @@ let log_dir_of_current_exe =
| Analyze -> "analyze" | Analyze -> "analyze"
| BuckCompilationDatabase -> "buck_compilation_database" | BuckCompilationDatabase -> "buck_compilation_database"
| Clang -> "clang" | Clang -> "clang"
| ClangWrapper -> "clang_wrapper"
| Interactive -> "interactive" | Interactive -> "interactive"
| Java -> "java" | Java -> "java"
| Llvm -> "llvm" | Llvm -> "llvm"
@ -1554,6 +1558,7 @@ let log_identifier_of_current_exe =
match CLOpt.current_exe with match CLOpt.current_exe with
| Analyze -> Option.map Filename.basename cluster_cmdline | Analyze -> Option.map Filename.basename cluster_cmdline
| Clang -> Option.map Filename.basename source_file | Clang -> Option.map Filename.basename source_file
| ClangWrapper
| Interactive | Interactive
| Java | Java
| Llvm | Llvm
@ -1576,7 +1581,8 @@ let log_files_of_current_exe =
let should_log_current_exe = let should_log_current_exe =
match CLOpt.current_exe with match CLOpt.current_exe with
| Analyze | Analyze
| Clang -> debug_mode || stats_mode | Clang
| ClangWrapper -> debug_mode || stats_mode
| BuckCompilationDatabase -> true | BuckCompilationDatabase -> true
| _ -> false | _ -> false

@ -0,0 +1,192 @@
/* Copyright (c) 2016 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/** Given a clang command, normalize it via `clang -###` if needed to get a clear view of what work
is being done and which source files are being compiled, if any, then replace compilation
commands by our own clang with our plugin attached for each source file. */
open! Utils;
/** where we are */
let exec_dir = Filename.dirname @@ Sys.executable_name;
let xx_suffix =
try (Sys.getenv "INFER_XX") {
| Not_found => ""
};
/** script to run our own clang */
let infer_clang_bin = exec_dir /\/ ("filter_args_and_run_fcp_clang" ^ xx_suffix);
/** script to attach the plugin to clang -cc1 commands and run InferClang */
let clang_cc1_capture = exec_dir /\/ "attach_plugin_and_run_clang_frontend.sh";
/** path to Apple's clang */
let apple_clang =
try (Some (Sys.getenv "FCP_APPLE_CLANG" ^ xx_suffix)) {
| Not_found => None
};
let typed_clang_invocation args => {
let is_assembly =
/* whether language is set to "assembler" or "assembler-with-cpp" */
{
let assembly_language =
Array.fold_left
(
fun (prev_arg, b) arg => {
let b' = b || string_equal "-x" prev_arg && string_is_prefix "assembler" arg;
(arg, b')
}
)
("", false)
args |> snd;
/* Detect -cc1as or assembly language commands. -cc1as is always the first argument if
present. */
string_equal args.(1) "-cc1as" || assembly_language
};
if is_assembly {
`Assembly args
} else if (args.(1) == "-cc1") {
`CC1
/* -cc1 is always the first argument if present. */
args
} else {
`NonCCCommand args
}
};
let commands_or_errors =
/* commands generated by `clang -### ...` start with ' "/absolute/path/to/binary"' */
Str.regexp " \"/\\|clang\\(\\|++\\): error:";
/** Given a list of arguments for clang [args], return a list of new commands to run according to
the results of `clang -### [args]`. */
let normalize args =>
switch (typed_clang_invocation args) {
| `CC1 args =>
Logging.out "InferClangWrapper got toplevel -cc1 command@\n";
[`CC1 args]
| `NonCCCommand args =>
let clang_hashhashhash =
String.concat
" " (IList.map Filename.quote [infer_clang_bin, "-###", ...Array.to_list args |> IList.tl]);
Logging.out "clang -### invocation: %s@\n" clang_hashhashhash;
let normalized_commands = ref [];
let one_line line =>
if (string_is_prefix " \"" line) {
/* massage line to remove edge-cases for splitting */
"\"" ^ line ^ " \"" |>
/* split by whitespace */
Str.split (Str.regexp_string "\" \"") |>
Array.of_list |>
typed_clang_invocation
} else {
`ClangError line
};
let consume_input i =>
try (
while true {
let line = input_line i;
/* keep only commands and errors */
if (Str.string_match commands_or_errors line 0) {
normalized_commands := [one_line line, ...!normalized_commands]
}
}
) {
| End_of_file => ()
};
/* collect stdout and stderr output together (in reverse order) */
with_process_full clang_hashhashhash consume_input consume_input |> ignore;
normalized_commands := IList.rev !normalized_commands;
/* Discard assembly commands. This may make the list of commands empty, in which case we'll run
the original clang command. We could be smarter about this and try to execute the assembly
commands with our own clang. */
IList.filter
(
fun
| `Assembly asm_cmd => {
Logging.out
"Skipping assembly command %s@\n" (String.concat " " @@ Array.to_list asm_cmd);
false
}
| _ => true
)
!normalized_commands
| `Assembly _ =>
/* discard assembly commands -- see above */
Logging.out "InferClangWrapper got toplevel assembly command@\n";
[]
};
let execute_clang_command clang_cmd =>
switch clang_cmd {
| `CC1 args =>
/* this command compiles some code; replace the invocation of clang with our own clang and
plugin */
args.(0) = clang_cc1_capture;
Logging.out "Executing -cc1 command: %s@\n" (String.concat " " @@ Array.to_list args);
Process.create_process_and_wait args
| `ClangError error =>
/* An error in the output of `clang -### ...`. Outputs the error and fail. This is because
`clang -###` pretty much never fails, but warns of failures on stderr instead. */
Logging.err "%s" error;
exit 1
| `Assembly args =>
/* We shouldn't get any assembly command at this point */
(
if Config.debug_mode {
failwithf
} else {
Logging.err
}
)
"WARNING: unexpected assembly command: %s@\n" (String.concat " " @@ Array.to_list args)
| `NonCCCommand args =>
/* Non-compilation (eg, linking) command. Run the command as-is. It will not get captured
further since `clang -### ...` will only output commands that invoke binaries using their
absolute paths. */
if Config.debug_mode {
Logging.out "Executing raw command: %s@\n" (String.concat " " @@ Array.to_list args)
};
Process.create_process_and_wait args
};
let () = {
let args = Sys.argv;
let commands = normalize args;
if (commands == []) {
/* No command to execute after -###, let's execute the original command
instead.
In particular, this can happen when
- there are only assembly commands to execute, which we skip, or
- the user tries to run `infer -- clang -c file_that_does_not_exist.c`. In this case, this
will fail with the appropriate error message from clang instead of silently analyzing 0
files. */
args.(0) = infer_clang_bin;
Logging.out
"WARNING: `clang -### <args>` returned\n an empty set of commands to run and no error. Will run the original command directly:@\n\n %s@\n"
(String.concat " " @@ Array.to_list args);
Process.create_process_and_wait args
} else {
IList.iter execute_clang_command commands
};
/* xcodebuild projects may require the object files to be generated by the Apple compiler, eg to
generate precompiled headers compatible with Apple's clang. */
switch apple_clang {
| None => ()
| Some bin =>
args.(0) = bin;
Process.create_process_and_wait args
}
};

@ -96,8 +96,8 @@ class BarClass {
// Whitelisted Objc class // Whitelisted Objc class
NSError* const e = nil; // no error NSError* const e = nil; // no error
NSError** e1; // no error NSError* __autoreleasing* e1; // no error
NSError const** e2; // no error NSError const* __autoreleasing* e2; // no error
NSError* const* e3 = &e; // no error NSError* const* e3 = &e; // no error
NSError* const* const e4 = &e; // no error NSError* const* const e4 = &e; // no error
} }

Loading…
Cancel
Save