Using the Slice Compiler

14 min read

3 min read

7 min read

5 min read

3 min read

4 min read

3 min read

2 min read

2 min read

Ice provides a Slice compiler for each language mapping. The compilers share a similar command-line syntax:

Shell
slice2<name> [options] file...

In MATLAB, you run the Slice-to-MATLAB compiler with the slice2matlab function of the Ice toolbox, as shown in Running the Compiler.

Regardless of which compiler you use, a number of command-line options are common to the compilers for any language mapping:

  • -h, --help Displays a help message.
  • -v, --version Displays the compiler version.
  • -DNAME Defines the preprocessor symbol NAME.
  • -DNAME=DEF Defines the preprocessor symbol NAME with the value DEF. Each compiler always predefines the __<compiler name in upper case>__ macro when compiling Slice file. For example, slice2cpp predefines __SLICE2CPP__.
  • -UNAME Undefines the preprocessor symbol NAME.
  • -IDIR Add the directory DIR to the search path for #include directives.
  • --output-dir DIR Place the generated files into directory DIR, which must already exist.
  • -d, --debug Print debug information showing the operation of the Slice parser.
  • --depend Print dependency information in Makefile format to standard output by default, or to the file specified by the --depend-file option.
  • --depend-xml Print dependency information in XML format to standard output by default, or to the file specified by the --depend-file option.
  • --depend-json Print dependency information in JSON format to standard output by default, or to the file specified by the --depend-file option.
  • --depend-file FILE Directs dependency information to the specified file. The output format depends on whether --depend, --depend-xml, or --depend-json is specified.

The Slice compilers permit you to compile more than a single source file, so you can compile several Slice definitions at once, for example:

Shell
slice2cpp -I. file1.ice file2.ice file3.ice

The Slice-to-C++ compiler (slice2cpp) offers the following additional options:

Changes the file extension for the generated header files from the default h to the extension specified by EXT.

You can also change the header file extension with a global metadata directive:

Slice
[["cpp:header-ext:hpp"]]
// ...

Only one such directive can appear in each source file. If you specify a header extension on both the command line and with a metadata directive, the metadata directive takes precedence. This ensures that included Slice files that were compiled separately get the correct header extension (provided that the included Slice files contain a corresponding metadata directive). For example:

Slice
// File example.ice
#include <Ice/BuiltinSequences.ice>
// ...

Compiling this file with

Shell
slice2cpp --header-ext=hpp -I/opt/Ice/include example.ice

generates example.hpp, but the #include directive in that file is for Ice/BuiltinSequences.h (not Ice/BuiltinSequences.hpp) because BuiltinSequences.ice contains the metadata directive [["cpp:header-ext:h"]].

You normally will not need to use this metadata directive. The directive is necessary only if:

  • You #include a Slice file in one of your own Slice files.
  • The included Slice file is part of a library you link against.
  • The library ships with the included Slice file's header.
  • The library header uses a different header extension than your own code.

For example, if the library uses .hpp as the header extension, but your own code uses .h, the library's Slice file should contain a [["cpp:header-ext:hpp"]] directive. (If the directive is missing, you can add it to the library's Slice file.)

Changes the file extension for the generated source files from the default cpp to the extension specified by EXT.

This option adds an include directive for the specified header at the beginning of the generated source file (preceding any other include directives). If GUARD is specified, the include directive is protected by the specified guard. For example, --add-header precompiled.h,__PRECOMPILED_H__ results in the following directives at the beginning of the generated source file:

C++
#ifndef __PRECOMPILED_H__
#define __PRECOMPILED_H__
#include <precompiled.h>
#endif

The option can be repeated to create include directives for several files.

As suggested by the preceding example, this option is useful mainly to integrate the generated code with a compiler's precompiled header mechanism.

Prepends the directory DIR to the path in the #include directive that the generated source file uses to include its own header file.

The #include directives generated by the Slice-to-C++ compiler can be a source of confusion if the semantics governing their generation are not well-understood. The generation of #include directives is influenced by the command-line options -I and --include-dir; these options are discussed in more detail below. The --output-dir option directs the translator to place all generated files in a particular directory, but has no impact on the contents of the generated code.

Given that the #include directives in header files and source files are generated using different semantics, we describe them in separate sections.

In most cases, the compiler generates the appropriate #include directives by default. As an example, suppose file A.ice includes B.ice using the following statement:

Slice
// A.ice
#include <B.ice>

Assuming both files are in the current working directory, we run the compiler as shown below:

Shell
slice2cpp -I. A.ice

The generated file A.h contains this #include directive:

C++
// A.h
#include <B.h>

If the proper include paths are specified to the C++ compiler, everything should compile correctly.

Similarly, consider the common case where A.ice includes B.ice from a subdirectory:

Slice
// A.ice
#include <inc/B.ice>

Assuming both files are in the inc subdirectory, we run the compiler as shown below:

Shell
slice2cpp -I. inc/A.ice

The default output of the compiler produces this #include directive in A.h:

C++
// A.h
#include <inc/B.h>

Again, it is the user's responsibility to ensure that the C++ compiler is configured to find inc/B.h during compilation.

Now let us consider a more complex example, in which we do not want the #include directive in the header file to match that of the Slice file. This can be necessary when the organizational structure of the Slice files does not match the application's C++ code. In such a case, the user may need to relocate the generated files from the directory in which they were created, and the #include directives must be aligned with the new structure.

For example, let us assume that B.ice is located in the subdirectory slice/inc:

Slice
// A.ice
#include <slice/inc/B.ice>

However, we do not want the slice subdirectory to appear in the #include directive generated in the header file, therefore we specify the additional compiler option -Islice:

Shell
slice2cpp -I. -Islice slice/inc/A.ice

The generated code demonstrates the impact of this extra option:

C++
// A.h
#include <inc/B.h>

As you can see, the #include directives generated in header files are affected by the include paths that you specify when running the compiler. Specifically, the include paths are used to abbreviate the path name in generated #include directives.

When translating an #include directive from a Slice file to a header file, the compiler compares each of the include paths against the path of the included file. If an include path matches the leading portion of the included file, the compiler removes that leading portion when generating the #include directive in the header file. If more than one include path matches, the compiler selects the one that results in the shortest path for the included file.

For example, suppose we had used the following options when compiling A.ice:

Shell
slice2cpp -I. -Islice -Islice/inc slice/inc/A.ice

In this case, the compiler compares all of the include paths against the included file slice/inc/B.ice and generates the following directive:

C++
// A.h
#include <B.h>

The option -Islice/inc produces the shortest result, therefore the default path for the included file (slice/inc/B.h) is replaced with B.h.

In general, the -I option plays two roles: it enables the preprocessor to locate included Slice files, and it provides you with a certain amount of control over the generated #include directives. In the last example above, the preprocessor locates slice/inc/B.ice using the include path specified by the -I. option. The remaining -I options do not help the preprocessor locate included files; they are simply hints to the compiler.

Finally, we recommend using caution when specifying include paths. If the preprocessor is able to locate an included file via multiple include paths, it always uses the first include path that successfully locates the file. If you intend to modify the generated #include directives by specifying extra -I options, you must ensure that your include path hints match the include path selected by the preprocessor to locate the included file. As a general rule, you should avoid specifying include paths that enable the preprocessor to locate a file in multiple ways.

By default, the generated source file includes its own header file with an #include directive that uses only the base name of the header file. This behavior is usually appropriate when the source file and header file reside in the same directory.

For example, suppose A.ice includes B.ice from a subdirectory, as shown in the following snippet of A.ice:

Slice
// A.ice
#include <inc/B.ice>

We generate the source file using this command:

Shell
slice2cpp -I. inc/A.ice

Upon examination, we see that the source file contains the following #include directive:

C++
// A.cpp
#include "A.h"

The #include directive for B.h appears in A.h, as described in Header Files.

However, suppose that we wish to enforce a particular standard for generated #include directives so that they are compatible with our C++ compiler's existing include path settings. In this case, we use the --include-dir option to modify the generated code. For example, consider the compiler command shown below:

Shell
slice2cpp --include-dir src -I. inc/A.ice

The source file now contains the following #include directive:

C++
// A.cpp
#include "src/A.h"

The directive still omits the directory of the Slice file, inc.

Ice for C++ ships with a CMake package that defines an imported library target for each Ice library, such as Ice::Ice and Ice::IceGrid, and the slice2cpp_generate function, which compiles the Slice files of a target as part of the build. For example:

cmake
cmake_minimum_required(VERSION 3.21)
project(greeter CXX)
find_package(Ice REQUIRED CONFIG)
add_executable(client Client.cpp Greeter.ice)
slice2cpp_generate(client)
target_link_libraries(client PRIVATE Ice::Ice)

The package requires CMake 3.21 or later. The CONFIG keyword makes find_package use config mode: it loads the package configuration file that Ice for C++ provides, rather than the FindIce find module that ships with CMake. Config mode is what CMake recommends for a project that provides its own package configuration (cmake-packages(7)).

If you installed Ice in a directory that CMake does not search by default, such as /opt/Ice, add that directory to CMAKE_PREFIX_PATH. On Windows, Ice for C++ is the ZeroC.Ice.Cpp NuGet package: set Ice_ROOT to the package's directory, such as packages/ZeroC.Ice.Cpp.3.8.3.

slice2cpp_generate(client):

  • compiles each Slice file in the sources of client with slice2cpp, and adds the generated header and source files to the target's sources;
  • adds the directory of the generated headers to the target's include directories, so Client.cpp can #include "Greeter.h";
  • passes Ice_SLICE_DIR, the directory of the Ice Slice files, to slice2cpp with -I, so #include <Ice/Identity.ice> resolves;
  • records the Slice files that each Slice file includes in a dependency file, so the build compiles a Slice file again when it, or a file it includes, changes.

Call slice2cpp_generate in the same CMakeLists.txt as the add_executable or add_library call that creates the target, after that call lists the target's Slice files.

ArgumentDescription
INCLUDE_DIRS <dir>...
Directories to search for included Slice files, passed to slice2cpp with -I.
OPTIONS <option>...
Other slice2cpp options, such as -D or --header-ext.
HEADER_OUTPUT_DIR <dir>
The directory for the generated headers, instead of the default under the build directory.
INCLUDE_DIR <dir>
The include prefix of the generated headers, passed to slice2cpp with --include-dir.
INCLUDE_SCOPE PRIVATE|PUBLIC
With PUBLIC, the targets that link this target also get the directories of the generated headers. The default is PRIVATE.
DEPENDS <dep>...
Other files whose changes recompile the Slice files.
GENERATED_HEADERS <var>, GENERATED_SOURCES <var>
Variables that receive the paths of the generated headers and sources.

The CMake package README covers the variables that find_package sets, copying the Ice DLLs on Windows, the generated file layout, and installing the generated headers.

The Slice-to-C# compiler (slice2cs) supports the following additional options:

  • --icerpc Generates code for IceRPC instead of Ice, in a file named <base name>.IceRpc.cs. See Using Ice and IceRPC Together.
  • --enable-analysis Omits the <auto-generated> comment from the generated files, which enables .NET code analysis of the generated code.

The Slice-to-Java compiler (slice2java) offers one additional option:

  • --list-generated Emit a list of generated files in XML format.

The com.zeroc.slice-tools Gradle plugin compiles the Slice files of a Java or Android project with slice2java and adds the generated Java files to the project's sources. Apply it after the Java or Android plugin:

Kotlin
plugins {
java
id("com.zeroc.slice-tools") version "3.8.+"
}

The plugin includes slice2java for Linux (x64 and arm64), macOS (arm64), and Windows (x64), as well as the Slice files of Ice, Glacier2, IceBox, IceGrid, IceStorm, and DataStorm. By default, it compiles your Slice files with the included slice2java and puts the included Slice files on the include path, so your Slice files can #include <Ice/Identity.ice>.

The slice block of the project holds the Slice source sets and the settings that apply to all of them:

SettingDescription
compilerArgs
Arguments the plugin passes to slice2java, such as -D options.
includeSearchPath
Directories the plugin passes to slice2java with -I.
sourceSets
The Slice source sets of the project, one for each Java or Android source set.
toolsPath
The directory that holds the slice2java to run instead of the included one, for example from a local Ice build or on a platform the plugin doesn't include. With toolsPath set, the included Slice files are not on the include path; add their directory to includeSearchPath when your Slice files include them.
Kotlin
slice {
includeSearchPath.from("../common/slice")
compilerArgs.addAll("-DUSE_TRACING")
}

The plugin adds a Slice source set to each Java source set, such as main and test, and to each Android source set, such as main, debug, and release. A Slice source set compiles the .ice files under src/<source set>/slice by default, and holds settings of its own:

SettingDescription
srcDir, srcDirs
Add directories of Slice files to the source set. setSrcDirs replaces the directories, including the default one.
includeSearchPath
Directories the plugin passes to slice2java with -I for this source set.
compilerArgs
Arguments the plugin passes to slice2java for this source set.

For each source set, the plugin passes the include search path and the compiler arguments of the project, followed by those of the source set. You configure a Slice source set in the slice block of its Java or Android source set:

Kotlin
sourceSets {
main {
slice {
srcDirs("../slice")
includeSearchPath.from("../common/slice")
}
}
}

The plugin registers a compileSlice<SourceSet> task for each source set, such as compileSliceMain, that generates Java files into build/generated/source/slice/<source set> in a Java project, and into build/generated/java/compileSlice<SourceSet> in an Android project. The compileSlice task runs all of them.

In a Java project, the plugin adds the Java files generated from each Slice source set to the Java sources of the same source set: the Java compiler compiles the files generated from src/test/slice with the test classes.

In an Android project, the plugin adds the generated Java files to the Java sources of each build variant, from the Slice source sets that contribute to the variant: main, the build type, each product flavor, the combination of the product flavors, and the combination of the product flavors and the build type. For example, the freeDebug variant of a project with a free product flavor compiles the files generated from src/main/slice, src/debug/slice, src/free/slice, and src/freeDebug/slice.

In a Groovy build script, you configure a Slice source set in the slice block of its Android source set:

Groovy
android {
sourceSets {
main {
slice {
srcDir "../slice"
}
}
}
}

In a Kotlin build script, Gradle generates the slice accessor only for Java source sets, so you configure the Slice source set of an Android source set through its extensions:

Kotlin
android {
sourceSets {
getByName("main") {
(this as ExtensionAware).extensions.configure<com.zeroc.slice.tools.SliceSourceSet>("slice") {
srcDir("../slice")
}
}
}
}

The Slice-to-JavaScript compiler (slice2js) offers the following command-line options in addition to the standard options:

  • --stdout Print generated code to standard output.
  • --typescript Generate TypeScript declaration file.
  • --depend-json Print dependency information in JSON format to standard output by default, or to the file specified by the --depend-file option. No code is generated when this option is specified. The output consists of the complete list of Slice files that the input Slice files depend on through direct or indirect inclusion.

The @zeroc/ice package includes the slice2js compiler for Linux (x64 and arm64), macOS (arm64), and Windows (x64). Run it with npx:

Shell
npx slice2js --output-dir src/generated slice/Greeter.ice

The slice2js command adds the Slice files of Ice, which the package includes, to the include search path, so your Slice files can include them, as in #include <Ice/Identity.ice>.

The optional @zeroc/slice2js package provides a plug-in that compiles your Slice files at the start of each build of a Vite, Rollup, webpack, or esbuild project. Install it as a development dependency:

Shell
npm install --save-dev @zeroc/slice2js

Import the plug-in for your bundler from its entry point:

BundlerEntry point
Vite
@zeroc/slice2js/unplugin/vite
Rollup
@zeroc/slice2js/unplugin/rollup
webpack
@zeroc/slice2js/unplugin/webpack
esbuild
@zeroc/slice2js/unplugin/esbuild

Then add the plug-in to the configuration of your bundler. For example, with Vite:

vite.config.js
import slice2js from "@zeroc/slice2js/unplugin/vite";
export default {
plugins: [
slice2js({
inputs: ["slice/*.ice"],
outputDir: "src/generated",
include: ["slice"],
args: ["--typescript"],
}),
],
};

The plug-in accepts the following options:

OptionDescription
inputs
The Slice files to compile, as file names or glob patterns. Required.
outputDir
The directory for the generated files. The plug-in creates it if it doesn't exist. Required.
include
Additional directories for the include search path of slice2js.
args
Additional slice2js command-line options, such as --typescript.
cwd
The directory against which the relative paths in inputs, outputDir, and include resolve. Defaults to the current directory.
toolsPath
The directory of a slice2js executable that replaces the one in the package.
slicePath
The directory of Slice files that replaces the Slice files of Ice in the include search path.

To compile Slice files from your own build script, call runSlice2js from @zeroc/slice2js/unplugin, which takes the same options as the plug-in, or compile from @zeroc/slice2js, which takes the slice2js command-line arguments and returns the exit code of the compiler:

JavaScript
import { runSlice2js } from "@zeroc/slice2js/unplugin";
await runSlice2js({ inputs: ["slice/*.ice"], outputDir: "src/generated", args: ["--typescript"] });

The Slice-to-MATLAB compiler offers two additional options:

  • --all Generate code for all Slice definitions, including those from included files.
  • --list-generated Emit a list of generated files in XML format.

slice2matlab does not support the --depend flag, although it does still support --depend-xml and --depend-file.

The Ice toolbox provides the Slice-to-MATLAB compiler and a slice2matlab function that runs it. You call this function from the MATLAB console or from a MATLAB script. It adds the directory of the Ice Slice files to the include search path, forwards its arguments to the compiler, and returns the compiler's exit status:

MATLAB
slice2matlab Greeter.ice
status = slice2matlab('--output-dir', 'generated', 'Greeter.ice');

The Slice-to-PHP compiler (slice2php) offers one additional option:

  • --all Generate code for all Slice definitions, including those from included files.

For each Slice file X.ice, slice2php generates PHP code into a file named X.php in the output directory. The default output directory is the current working directory, but a different directory can be specified using the --output-dir option.

It is important to understand how slice2php handles include files. In the absence of the --all option, the compiler does not generate PHP code for Slice definitions in included files. Rather, the compiler translates Slice #include statements into PHP require statements in the following manner:

  1. Determine the full pathname of the included file.
  2. Create the shortest possible relative pathname for the included file by iterating over each of the include directories (specified using the -I option) and removing the leading directory from the included file if possible. For example, if the full pathname of an included file is /opt/App/slice/OS/Process.ice, and we specified the options -I/opt/App and -I/opt/App/slice, then the shortest relative pathname is OS/Process.ice after removing /opt/App/slice.
  3. Replace the .ice extension with .php. Continuing our example from the previous step, the translated require statement becomes
PHP
require_once "OS/Process.php";

As a result, you can use -I options to tailor the require statements generated by the compiler in order to avoid absolute path names and match the organizational structure of your application's source files.

The Slice-to-Python compiler (slice2py) supports the following additional options:

  • --build modules|index|all Controls what type of Python files are generated from the compile Slice files.

    • --build=modules Generates only the Python module files for the Slice definitions.
    • --build=index Generates only the Python package index files (__init__.py).
    • --build=all Generates both module and index files (this is the default if --build is omitted).
  • --list-generated modules|index|all Lists the Python files that would be generated for the given Slice definitions, without producing any output files.

    • --list-generated=modules Lists only the Python module files for the Slice definitions.
    • --list-generated=index Lists only the Python package index files (__init__.py).
    • --list-generated=all Lists both module and index files.

The Slice-to-Swift compiler (slice2swift) supports only the common Slice compiler options.