This patch adds D language support to the GCC testsuite. As well as generating the DejaGNU options for compile and link tests, handles the conversion from DMD-style compiler options to GDC. --- gcc/testsuite/gdc.test/d_do_test.exp | 404 +++++++++++++++++++++++++++ gcc/testsuite/lib/gdc-dg.exp | 88 ++++++ gcc/testsuite/lib/gdc.exp | 277 ++++++++++++++++++ 3 files changed, 769 insertions(+) create mode 100644 gcc/testsuite/gdc.test/d_do_test.exp create mode 100644 gcc/testsuite/lib/gdc-dg.exp create mode 100644 gcc/testsuite/lib/gdc.exp diff --git a/gcc/testsuite/gdc.test/d_do_test.exp b/gcc/testsuite/gdc.test/d_do_test.exp new file mode 100644 index 00000000000..a3c49165bbd --- /dev/null +++ b/gcc/testsuite/gdc.test/d_do_test.exp @@ -0,0 +1,404 @@ +# Copyright (C) 2012-2018 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# . + +# Test using the DMD testsuite. +# Load support procs. +load_lib gdc-dg.exp + +# +# Convert DMD arguments to GDC equivalent +# + +proc gdc-convert-args { base args } { + set out "" + + foreach arg [split [lindex $args 0] " "] { + # List of switches kept in ASCII collated order. + if { [regexp -- {^-I([\w+/-]+)} $arg pattern path] } { + lappend out "-I$base/$path" + + } elseif { [regexp -- {^-J([\w+/-]+)} $arg pattern path] } { + lappend out "-J$base/$path" + + } elseif [string match "-allinst" $arg] { + lappend out "-fall-instantiations" + + } elseif { [string match "-boundscheck" $arg] + || [string match "-boundscheck=on" $arg] } { + lappend out "-fbounds-check" + + } elseif { [string match "-boundscheck=off" $arg] + || [string match "-noboundscheck" $arg] } { + lappend out "-fno-bounds-check" + + } elseif [string match "-boundscheck=safeonly" $arg] { + lappend out "-fbounds-check=safeonly" + + } elseif [string match "-c" $arg] { + lappend out "-c" + + } elseif [string match "-d" $arg] { + lappend out "-Wno-deprecated" + + } elseif [string match "-de" $arg] { + lappend out "-Wdeprecated" + lappend out "-Werror" + + } elseif [string match "-debug" $arg] { + lappend out "-fdebug" + + } elseif [regexp -- {^-debug=(\w+)} $arg pattern value] { + lappend out "-fdebug=$value" + + } elseif [string match "-dip1000" $arg] { + lappend out "-ftransition=dip1000" + + } elseif [string match "-dip25" $arg] { + lappend out "-ftransition=dip25" + + } elseif [string match "-dw" $arg] { + lappend out "-Wdeprecated" + lappend out "-Wno-error" + + } elseif [string match "-fPIC" $arg] { + lappend out "-fPIC" + + } elseif { [string match "-g" $arg] + || [string match "-gc" $arg] } { + lappend out "-g" + + } elseif [string match "-inline" $arg] { + lappend out "-finline-functions" + + } elseif [string match "-main" $arg] { + lappend out "-fmain" + + } elseif [regexp -- {^-mv=([\w+=./-]+)} $arg pattern value] { + lappend out "-fmodule-file=$value" + + } elseif [string match "-O" $arg] { + lappend out "-O2" + + } elseif [string match "-release" $arg] { + lappend out "-frelease" + + } elseif [regexp -- {^-transition=(\w+)} $arg pattern value] { + lappend out "-ftransition=$value" + + } elseif [string match "-unittest" $arg] { + lappend out "-funittest" + + } elseif [string match "-verrors=spec" $arg] { + lappend out "-Wspeculative" + + } elseif [regexp -- {^-verrors=(\d+)} $arg pattern num] { + lappend out "-fmax-errors=$num" + + } elseif [regexp -- {^-version=(\w+)} $arg pattern value] { + lappend out "-fversion=$value" + + } elseif [string match "-vtls" $arg] { + lappend out "-ftransition=tls" + + } elseif [string match "-w" $arg] { + lappend out "-Wall" + lappend out "-Werror" + + } elseif [string match "-wi" $arg] { + lappend out "-Wall" + lappend out "-Wno-error" + + } else { + # print "Unhandled Argument: $arg" + } + } + + return $out +} + +proc gdc-copy-extra { base extra } { + # Split base, folder/file. + set type [file dirname $extra] + + # print "Filename: $base - $extra" + + set fdin [open $base/$extra r] + fconfigure $fdin -encoding binary + + file mkdir $type + set fdout [open $extra w] + fconfigure $fdout -encoding binary + + while { [gets $fdin copy_line] >= 0 } { + set out_line $copy_line + puts $fdout $out_line + } + + close $fdin + close $fdout + + return $extra +} + +# +# Translate DMD test directives to dejagnu equivalent. +# +# COMPILE_SEPARATELY: Not handled. +# EXECUTE_ARGS: Parameters to add to the execution of the test. +# EXTRA_SOURCES: List of extra sources to build and link along with +# the test. +# EXTRA_FILES: List of extra files to copy for the test runs. +# PERMUTE_ARGS: The set of arguments to permute in multiple compiler +# invocations. An empty set means only one permutation +# with no arguments. +# TEST_OUTPUT: The output expected from the compilation. +# POST_SCRIPT: Not handled. +# REQUIRED_ARGS: Arguments to add to the compiler command line. +# DISABLED: Not handled. +# + +proc dmd2dg { base test } { + global DEFAULT_DFLAGS + global PERMUTE_ARGS + global GDC_EXECUTE_ARGS + + set PERMUTE_ARGS $DEFAULT_DFLAGS + set GDC_EXECUTE_ARGS "" + + # Split base, folder/file. + set type [file dirname $test] + + # print "Filename: $base - $test" + + set fdin [open $base/$test r] + #fconfigure $fdin -encoding binary + + file mkdir $type + set fdout [open $test w] + #fconfigure $fdout -encoding binary + + while { [gets $fdin copy_line] >= 0 } { + set out_line $copy_line + + if [regexp -- {COMPILE_SEPARATELY} $copy_line] { + # COMPILE_SEPARATELY is not handled. + regsub -- {COMPILE_SEPARATELY.*$} $copy_line "" out_line + + } elseif [regexp -- {DISABLED} $copy_line] { + # DISABLED is not handled. + regsub -- {DISABLED.*$} $copy_line "" out_line + + } elseif [regexp -- {POST_SCRIPT} $copy_line] { + # POST_SCRIPT is not handled + regsub -- {POST_SCRIPT.*$} $copy_line "" out_line + + } elseif [regexp -- {PERMUTE_ARGS\s*:\s*(.*)} $copy_line match args] { + # PERMUTE_ARGS is handled by gdc-do-test. + set PERMUTE_ARGS [gdc-convert-args $base $args] + regsub -- {PERMUTE_ARGS.*$} $copy_line "" out_line + + } elseif [regexp -- {EXECUTE_ARGS\s*:\s*(.*)} $copy_line match args] { + # EXECUTE_ARGS is handled by gdc_load. + foreach arg $args { + lappend GDC_EXECUTE_ARGS $arg + } + regsub -- {EXECUTE_ARGS.*$} $copy_line "" out_line + + } elseif [regexp -- {REQUIRED_ARGS\s*:\s*(.*)} $copy_line match args] { + # Convert all listed arguments to from dmd to gdc-style. + set new_option "{ dg-additional-options \"[gdc-convert-args $base $args]\" }" + regsub -- {REQUIRED_ARGS.*$} $copy_line $new_option out_line + + } elseif [regexp -- {EXTRA_SOURCES\s*:\s*(.*)} $copy_line match sources] { + # Copy all sources to the testsuite build directory. + foreach import $sources { + # print "Import: $base $type/$import" + gdc-copy-extra $base "$type/$import" + } + set new_option "{ dg-additional-sources \"$sources\" }" + regsub -- {EXTRA_SOURCES.*$} $copy_line $new_option out_line + + } elseif [regexp -- {EXTRA_CPP_SOURCES\s*:\s*(.*)} $copy_line match sources] { + # Copy all sources to the testsuite build directory. + foreach import $sources { + # print "Import: $base $type/$import" + gdc-copy-extra $base "$type/$import" + } + set new_option "{ dg-additional-sources \"$sources\" }" + regsub -- {EXTRA_CPP_SOURCES.*$} $copy_line $new_option out_line + + } elseif [regexp -- {EXTRA_FILES\s*:\s*(.*)} $copy_line match files] { + # Copy all sources to the testsuite build directory. + foreach import $files { + # print "Import: $base $type/$import" + gdc-copy-extra $base "$type/$import" + } + set new_option "{ dg-additional-files \"$files\" }" + regsub -- {EXTRA_FILES.*$} $copy_line $new_option out_line + + } + + puts $fdout $out_line + } + + # Add specific options for test type + + # DMD's testsuite is extremely verbose, compiler messages from constructs + # such as pragma(msg, ...) would otherwise cause tests to fail. + set out_line "// { dg-prune-output .* }" + puts $fdout $out_line + + # Since GCC 6-20160131 blank lines are not allowed in the output by default. + dg-allow-blank-lines-in-output { 1 } + + # Compilable files are successful if an output is generated. + # Fail compilable are successful if an output is not generated. + # Runnable must compile, link, and return 0 to be successful by default. + switch [file dirname $test] { + runnable { + if ![isnative] { + set out_line "// { dg-final { output-exists } }" + puts $fdout $out_line + } + } + + compilable { + set out_line "// { dg-final { output-exists } }" + puts $fdout $out_line + } + + fail_compilation { + set out_line "// { dg-final { output-exists-not } }" + puts $fdout $out_line + } + } + + close $fdin + close $fdout + + return $test +} + +proc gdc-permute-options { options } { + set result { } + set n [expr 1<<[llength $options]] + for { set i 0 } { $i<$n } { incr i } { + set option "" + for { set j 0 } { $j<[llength $options] } { incr j } { + if [expr $i & 1 << $j] { + append option [lindex $options $j] + append option " " + } + } + lappend result $option + + } + return $result +} + + +proc gdc-do-test { } { + global srcdir subdir + global dg-do-what-default + global verbose + + # If a testcase doesn't have special options, use these. + global DEFAULT_DFLAGS + if ![info exists DEFAULT_DFLAGS] then { + set DEFAULT_DFLAGS "-g -O2 -frelease" + #set DEFAULT_DFLAGS "-O2" + } + + # These are special options to use on testcase, and override DEFAULT_DFLAGS + global PERMUTE_ARGS + + # Set if an extra option should be passed to link to shared druntime. + global SHARED_OPTION + + # Additional arguments for gdc_load + global GDC_EXECUTE_ARGS + + # Initialize `dg'. + dg-init + + # Main loop. + + # set verbose 1 + # set dg-final-code "" + # Find all tests and pass to routine. + foreach test [lsort [find $srcdir/$subdir *]] { + regexp -- "(.*)/(.+)/(.+)\.(.+)$" $test match base dir name ext + + # Skip invalid test directory + if { [lsearch "runnable compilable fail_compilation" $dir] == -1 } { + continue + } + + # Skip invalid test extensions + if { [lsearch "d" $ext] == -1 } { + continue + } + + # Convert to DG test. + set imports [format "-I%s/%s" $base $dir] + set filename [dmd2dg $base $dir/$name.$ext] + + if { $dir == "runnable" } { + append PERMUTE_ARGS " $SHARED_OPTION" + } + set options [gdc-permute-options $PERMUTE_ARGS] + + switch $dir { + runnable { + for { set i 0 } { $i<[llength $options] } { incr i } { + set flags [lindex $options $i] + if [isnative] { + set dg-do-what-default "run" + } else { + set dg-do-what-default "link" + } + gdc-dg-runtest $filename $flags $imports + } + } + + compilable { + for { set i 0 } { $i<[llength $options] } { incr i } { + set flags [lindex $options $i] + #set dg-do-what-default "compile" + set dg-do-what-default "assemble" + gdc-dg-runtest $filename $flags $imports + } + } + + fail_compilation { + for { set i 0 } { $i<[llength $options] } { incr i } { + set flags [lindex $options $i] + set dg-do-what-default "assemble" + gdc-dg-runtest $filename $flags $imports + } + } + } + + # Cleanup + #file delete $filename + } + + # All done. + dg-finish +} + +gdc-do-test + diff --git a/gcc/testsuite/lib/gdc-dg.exp b/gcc/testsuite/lib/gdc-dg.exp new file mode 100644 index 00000000000..e5d9f1d9cb7 --- /dev/null +++ b/gcc/testsuite/lib/gdc-dg.exp @@ -0,0 +1,88 @@ +# Copyright (C) 2012-2018 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# . + +load_lib gcc-dg.exp + +# Define gdc callbacks for dg.exp. + +proc gdc-dg-test { prog do_what extra_tool_flags } { + set result \ + [gcc-dg-test-1 gdc_target_compile $prog $do_what $extra_tool_flags] + + set comp_output [lindex $result 0] + set output_file [lindex $result 1] + + return [list $comp_output $output_file] +} + +proc gdc-dg-prune { system text } { + return [gcc-dg-prune $system $text] +} + +# Utility routines. + +# +# Modified dg-runtest that can cycle through a list of optimization options +# as c-torture does. +# + +proc gdc-dg-runtest { testcases flags default-extra-flags } { + global runtests + + foreach test $testcases { + # If we're only testing specific files and this isn't one of + # them, skip it. + if ![runtest_file_p $runtests $test] { + continue + } + + # Use TORTURE_OPTIONS to cycle through an option list. + if [torture-options-exist] then { + global torture_with_loops + set option_list $torture_with_loops + } else { + set option_list { "" } + } + + set nshort [file tail [file dirname $test]]/[file tail $test] + + foreach flags_t $option_list { + verbose "Testing $nshort, $flags $flags_t" 1 + dg-test $test "$flags $flags_t" ${default-extra-flags} + } + } +} + +# +# gdc_load -- wrapper around default gdc_load to handle tests that +# require program arguments passed to them. +# + +if { [info procs gdc_load] != [list] \ + && [info procs prev_gdc_load] == [list] } { + rename gdc_load prev_gdc_load + + proc gdc_load { program args } { + global GDC_EXECUTE_ARGS + if [info exists GDC_EXECUTE_ARGS] then { + set args [concat "{$GDC_EXECUTE_ARGS}"] + } + #print "Running: $program [lindex $args 0]" + set result [eval [list prev_gdc_load $program] $args ] + return $result + } +} + diff --git a/gcc/testsuite/lib/gdc.exp b/gcc/testsuite/lib/gdc.exp new file mode 100644 index 00000000000..425cb4a61e3 --- /dev/null +++ b/gcc/testsuite/lib/gdc.exp @@ -0,0 +1,277 @@ +# Copyright (C) 2012-2018 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# . + +# +# gdc support library routines +# + +load_lib prune.exp +load_lib gcc-defs.exp +load_lib timeout.exp +load_lib target-libpath.exp + +# +# GDC_UNDER_TEST is the compiler under test. +# + +set gdc_compile_options "" + + +# +# gdc_version -- extract and print the version number of the compiler +# + +proc gdc_version { } { + global GDC_UNDER_TEST + + gdc_init + + # ignore any arguments after the command + set compiler [lindex $GDC_UNDER_TEST 0] + + # verify that the compiler exists + if { [is_remote host] || [which $compiler] != 0 } then { + set tmp [remote_exec host "$compiler -v"] + set status [lindex $tmp 0] + set output [lindex $tmp 1] + regexp " version \[^\n\r\]*" $output version + if { $status == 0 && [info exists version] } then { + if [is_remote host] { + clone_output "$compiler $version\n" + } else { + clone_output "[which $compiler] $version\n" + } + } else { + clone_output "Couldn't determine version of [which $compiler]\n" + } + } else { + # compiler does not exist (this should have already been detected) + warning "$compiler does not exist" + } +} + +# +# gdc_include_flags -- include flags for the gcc tree structure +# + +proc gdc_include_flags { paths } { + global srcdir + global TESTING_IN_BUILD_TREE + + set flags "" + + if { [is_remote host] || ![info exists TESTING_IN_BUILD_TREE] } { + return "${flags}" + } + + set gccpath ${paths} + set target [file tail [file normalize ${paths}]] + + if { $gccpath != "" } { + if [file exists "${gccpath}/libphobos/libdruntime"] { + append flags "-I${gccpath}/libphobos/libdruntime " + } + } + append flags "-I${srcdir}/../../libphobos/libdruntime " + append flags "-I${srcdir}/../../libphobos/src " + + # For the tests that mix C++ and D, we should try and handle this better. + if { $gccpath != "" } { + if [file exists "${gccpath}/libstdc++-v3/include"] { + append flags "-I${gccpath}/libstdc++-v3/include " + append flags "-I${gccpath}/libstdc++-v3/include/$target " + } + } + append flags "-I${srcdir}/../../libstdc++-v3/libsupc++" +} + +# +# gdc_link_flags -- linker flags for the gcc tree structure +# + +proc gdc_link_flags { paths } { + global srcdir + global ld_library_path + global GDC_UNDER_TEST + global shlib_ext + global SHARED_OPTION + + set gccpath ${paths} + set libio_dir "" + set flags "" + set ld_library_path "." + set shlib_ext [get_shlib_extension] + set SHARED_OPTION "" + verbose "shared lib extension: $shlib_ext" + + if { $gccpath != "" } { + # Path to libgphobos.spec. + append flags "-B${gccpath}/libphobos/src " + + if { [file exists "${gccpath}/libphobos/src/.libs/libgphobos.a"] \ + || [file exists "${gccpath}/libphobos/src/.libs/libgphobos.${shlib_ext}"] } { + append flags "-L${gccpath}/libphobos/src/.libs " + append ld_library_path ":${gccpath}/libphobos/src/.libs" + } + if { [file exists "${gccpath}/libphobos/libdruntime/.libs/libgdruntime.a"] \ + || [file exists "${gccpath}/libphobos/libdruntime/.libs/libgdruntime.${shlib_ext}"] } { + append flags "-L${gccpath}/libphobos/libdruntime/.libs " + append ld_library_path ":${gccpath}/libphobos/libdruntime/.libs" + } + # Static linking is default. If only the shared lib is available adjust + # flags to always use it. If both are available, set SHARED_OPTION which + # will be added to PERMUTE_ARGS + if { [file exists "${gccpath}/libphobos/libdruntime/.libs/libgdruntime.${shlib_ext}"] } { + if { [file exists "${gccpath}/libphobos/libdruntime/.libs/libgdruntime.a"] } { + set SHARED_OPTION "-shared-libphobos" + } else { + append flags "-shared-libphobos " + } + } + if [file exists "${gccpath}/libiberty/libiberty.a"] { + append flags "-L${gccpath}/libiberty " + } + # For the tests that mix C++ and D, we should try and handle this better. + if { [file exists "${gccpath}/libstdc++-v3/src/.libs/libstdc++.a"] \ + || [file exists "${gccpath}/libstdc++-v3/src/.libs/libstdc++.${shlib_ext}"] } { + append flags "-L${gccpath}/libstdc++-v3/src/.libs " + append ld_library_path ":${gccpath}/libstdc++-v3/src/.libs" + } + append ld_library_path [gcc-set-multilib-library-path $GDC_UNDER_TEST] + } else { + global tool_root_dir + + set libphobos [lookfor_file ${tool_root_dir} libgphobos] + if { $libphobos != "" } { + append flags "-B${libphobos} -L${libphobos} " + append ld_library_path ":${libphobos}" + } + set libdruntime [lookfor_file ${tool_root_dir} libgdruntime] + if { $libdruntime != "" } { + append flags "-L${libdruntime} " + append ld_library_path ":${libdruntime}" + } + set libiberty [lookfor_file ${tool_root_dir} libiberty] + if { $libiberty != "" } { + append flags "-L${libiberty} " + } + } + + set_ld_library_path_env_vars + + return "$flags" +} + +# +# gdc_init -- called at the start of each subdir of tests +# + +proc gdc_init { args } { + global subdir + global gdc_initialized + global base_dir + global tmpdir + global libdir + global gluefile wrap_flags + global objdir srcdir + global ALWAYS_DFLAGS + global TOOL_EXECUTABLE TOOL_OPTIONS + global GDC_UNDER_TEST + global TESTING_IN_BUILD_TREE + global TEST_ALWAYS_FLAGS + + # We set LC_ALL and LANG to C so that we get the same error messages as expected. + setenv LC_ALL C + setenv LANG C + + if ![info exists GDC_UNDER_TEST] then { + if [info exists TOOL_EXECUTABLE] { + set GDC_UNDER_TEST $TOOL_EXECUTABLE + } else { + if { [is_remote host] || ! [info exists TESTING_IN_BUILD_TREE] } { + set GDC_UNDER_TEST [transform gdc] + } else { + set GDC_UNDER_TEST [findfile $base_dir/../../gdc "$base_dir/../../gdc -B$base_dir/../../" [findfile $base_dir/gdc "$base_dir/gdc -B$base_dir/" [transform gdc]]] + } + } + } + + if ![is_remote host] { + if { [which $GDC_UNDER_TEST] == 0 } then { + perror "GDC_UNDER_TEST ($GDC_UNDER_TEST) does not exist" + exit 1 + } + } + if ![info exists tmpdir] { + set tmpdir "/tmp" + } + + if [info exists gluefile] { + unset gluefile + } + + gdc_maybe_build_wrapper "${tmpdir}/d-testglue.o" + + set ALWAYS_DFLAGS "" + + # TEST_ALWAYS_FLAGS are flags that should be passed to every + # compilation. They are passed first to allow individual + # tests to override them. + if [info exists TEST_ALWAYS_FLAGS] { + lappend ALWAYS_DFLAGS "additional_flags=$TEST_ALWAYS_FLAGS" + } + + if ![is_remote host] { + if [info exists TOOL_OPTIONS] { + lappend ALWAYS_DFLAGS "additional_flags=[gdc_include_flags [get_multilibs ${TOOL_OPTIONS}] ]" + lappend ALWAYS_DFLAGS "ldflags=[gdc_link_flags [get_multilibs ${TOOL_OPTIONS}] ]" + } else { + lappend ALWAYS_DFLAGS "additional_flags=[gdc_include_flags [get_multilibs] ]" + lappend ALWAYS_DFLAGS "ldflags=[gdc_link_flags [get_multilibs] ]" + } + } + + if [info exists TOOL_OPTIONS] { + lappend ALWAYS_DFLAGS "additional_flags=$TOOL_OPTIONS" + } + + verbose -log "ALWAYS_DFLAGS set to $ALWAYS_DFLAGS" + + verbose "gdc is initialized" 3 +} + +# +# gdc_target_compile -- compile a source file +# + +proc gdc_target_compile { source dest type options } { + global tmpdir + global gluefile wrap_flags + global ALWAYS_DFLAGS + global GDC_UNDER_TEST + + if { [target_info needs_status_wrapper] != "" && [info exists gluefile] } { + lappend options "libs=${gluefile}" + lappend options "ldflags=${wrap_flags}" + } + + lappend options "timeout=[timeout_value]" + lappend options "compiler=$GDC_UNDER_TEST" + + set options [concat "$ALWAYS_DFLAGS" $options] + set options [dg-additional-files-options $options $source] + return [target_compile $source $dest $type $options] +} -- 2.17.1