environmentVariables;
-
- /**
- * Path to the `cargo` command. If unset or set to "cargo", uses $PATH.
- */
- @Parameter(property = "cargoPath", defaultValue = "cargo")
- private String cargoPath;
-
- /**
- * Path to the Rust crate (or workspace) to build.
- */
- @Parameter(property = "path", required = true)
- private String path;
-
- /**
- * Build artifacts in release mode, with optimizations.
- * Defaults to "false" and creates a debug build.
- * Equivalent to Cargo's `--release` option.
- */
- @Parameter(property = "release", defaultValue = "false")
- private boolean release;
-
- /**
- * List of features to activate.
- * If not specified, default features are activated.
- * Equivalent to Cargo's `--features` option.
- */
- @Parameter(property = "features")
- private String[] features;
-
- /**
- * Activate all available features.
- * Defaults to "false".
- * Equivalent to Cargo's `--all-features` option.
- */
- @Parameter(property = "all-features", defaultValue = "false")
- private boolean allFeatures;
-
- /**
- * Do not activate the `default` feature.
- * Defaults to "false".
- * Equivalent to Cargo's `--no-default-features` option.
- */
- @Parameter(property = "no-default-features", defaultValue = "false")
- private boolean noDefaultFeatures;
-
- /**
- * Build all tests.
- * Defaults to "false".
- * Equivalent to Cargo's `--tests` option.
- */
- @Parameter(property = "tests", defaultValue = "false")
- private boolean tests;
-
- /**
- * Additional args to pass to cargo.
- */
- @Parameter(property = "extra-args")
- private String[] extraArgs;
-
+public class CargoBuildMojo extends CargoMojoBase {
/**
* Location to copy the built Rust binaries to.
* If unset, the binaries are not copied and remain in the target directory.
- *
+ *
* See also `copyWithPlatformDir`.
*/
@Parameter(property = "copyTo")
private String copyTo;
/**
- * Further nest copy into a subdirectory named through the following expression:
- * (System.getProperty("os.name") + "_" + System.getProperty("os.arch")).toLowerCase();
- *
+ * Further nest copy into a child directory named through the target's platform.
+ * The computed name matches that of the `io.questdb.jar.jni.OsInfo.platform()` method.
+ *
* See also `copyTo`.
*/
@Parameter(property = "copyWithPlatformDir")
private boolean copyWithPlatformDir;
- private String getCargoPath() {
- String path = cargoPath;
-
- final boolean isWindows = System.getProperty("os.name")
- .toLowerCase().startsWith("windows");
-
- // Expand "~" to user's home directory.
- // This works around a limitation of ProcessBuilder.
- if (!isWindows && cargoPath.startsWith("~/")) {
- path = System.getProperty("user.home") + cargoPath.substring(1);
- }
-
- return path;
- }
-
- private File getPath() {
- File file = new File(path);
- if (file.isAbsolute()) {
- return file;
- } else {
- return new File(project.getBasedir(), path);
- }
- }
-
- private String getName() {
- final String[] components = path.split("/");
- return components[components.length - 1];
- }
-
- private File getTargetDir() {
- return new File(new File(new File(project.getBuild().getDirectory()), "rust-maven-plugin"), getName());
- }
-
- private void runCommand(List args)
- throws IOException, InterruptedException, MojoExecutionException {
- final ProcessBuilder processBuilder = new ProcessBuilder(args);
- processBuilder.redirectErrorStream(true);
- processBuilder.environment().putAll(environmentVariables);
-
- // Set the current working directory for the cargo command.
- processBuilder.directory(getPath());
- final Process process = processBuilder.start();
- Log log = getLog();
- Executors.newSingleThreadExecutor().submit(() -> {
- new BufferedReader(new InputStreamReader(process.getInputStream()))
- .lines()
- .forEach(log::info);
- });
-
- final int exitCode = process.waitFor();
- if (exitCode != 0) {
- throw new MojoExecutionException("Cargo command failed with exit code " + exitCode);
- }
- }
-
- private void cargo(List args) throws MojoExecutionException {
- String cargoPath = getCargoPath();
- final List cmd = new ArrayList<>();
- cmd.add(cargoPath);
- cmd.addAll(args);
- getLog().info("Working directory: " + getPath());
- if (!environmentVariables.isEmpty()) {
- getLog().info("Environment variables:");
- for (String key : environmentVariables.keySet()) {
- getLog().info(" " + key + "=" + Shlex.quote(environmentVariables.get(key)));
- }
- }
- getLog().info("Running: " + Shlex.quote(cmd));
- try {
- runCommand(cmd);
- } catch (IOException | InterruptedException e) {
- CargoInstalledChecker.INSTANCE.check(getLog(), cargoPath);
- throw new MojoExecutionException("Failed to invoke cargo", e);
- }
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ final Crate crate = new Crate(
+ getCrateRoot(),
+ getTargetRootDir(),
+ extractCrateParams());
+ crate.setLog(getLog());
+ crate.build();
+ crate.copyArtifacts();
}
- private File getCopyToDir() throws MojoExecutionException {
- File copyToDir = new File(copyTo);
- if (!copyToDir.isAbsolute()) {
- copyToDir = new File(project.getBasedir(), copyTo);
- }
- if (copyWithPlatformDir) {
- final String osName = System.getProperty("os.name").toLowerCase();
- final String osArch = System.getProperty("os.arch").toLowerCase();
- final String platform = (osName + "-" + osArch).replace(' ', '_');
- copyToDir = new File(copyToDir, platform);
- }
- if (!copyToDir.exists()) {
- if (!copyToDir.mkdirs()) {
- throw new MojoExecutionException("Failed to create directory " + copyToDir);
+ private Crate.Params extractCrateParams() throws MojoExecutionException {
+ final Crate.Params params = getCommonCrateParams();
+ if (copyTo != null) {
+ Path copyToDir = Paths.get(copyTo);
+ if (!copyToDir.isAbsolute()) {
+ copyToDir = project.getBasedir().toPath()
+ .resolve(copyToDir);
}
+ params.copyToDir = copyToDir;
}
- if (!copyToDir.isDirectory()) {
- throw new MojoExecutionException(copyToDir + " is not a directory");
- }
- return copyToDir;
- }
-
- @Override
- public void execute() throws MojoExecutionException {
- List args = new ArrayList<>();
- args.add("build");
-
- args.add("--target-dir");
- args.add(getTargetDir().getAbsolutePath());
-
- if (release) {
- args.add("--release");
- }
-
- if (allFeatures) {
- args.add("--all-features");
- }
-
- if (noDefaultFeatures) {
- args.add("--no-default-features");
- }
-
- if (features != null && features.length > 0) {
- args.add("--features");
- args.add(String.join(",", features));
- }
-
- if (tests) {
- args.add("--tests");
- }
-
- if (extraArgs != null) {
- Collections.addAll(args, extraArgs);
- }
- cargo(args);
-
- // if/when --out-dir is stabilized then the outputRedirector function should be replaced with just the following args:
- // args.add("--out-dir")
- // args.add(getCopyToDir());
-
- new ManualOutputRedirector(getLog(), getName(), release, getPath(), getTargetDir(), getCopyToDir()).copyArtifacts();
+ params.copyWithPlatformDir = copyWithPlatformDir;
+ return params;
}
}
diff --git a/rust-maven-plugin/src/main/java/io/questdb/maven/rust/CargoMojoBase.java b/rust-maven-plugin/src/main/java/io/questdb/maven/rust/CargoMojoBase.java
new file mode 100644
index 0000000..61e14dc
--- /dev/null
+++ b/rust-maven-plugin/src/main/java/io/questdb/maven/rust/CargoMojoBase.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * ___ _ ____ ____
+ * / _ \ _ _ ___ ___| |_| _ \| __ )
+ * | | | | | | |/ _ \/ __| __| | | | _ \
+ * | |_| | |_| | __/\__ \ |_| |_| | |_) |
+ * \__\_\\__,_|\___||___/\__|____/|____/
+ *
+ * Copyright (c) 2014-2019 Appsicle
+ * Copyright (c) 2019-2023 QuestDB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+package io.questdb.maven.rust;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+
+
+public abstract class CargoMojoBase extends AbstractMojo {
+ @Parameter(property = "project", readonly = true)
+ protected MavenProject project;
+
+ @Parameter(property = "environmentVariables")
+ private HashMap environmentVariables;
+
+ /**
+ * Path to the `cargo` command. If unset or set to "cargo", uses $PATH.
+ */
+ @Parameter(property = "cargoPath", defaultValue = "cargo")
+ private String cargoPath;
+
+ /**
+ * Path to the Rust crate to build.
+ */
+ @Parameter(property = "path", required = true)
+ private String path;
+
+ /**
+ * Build artifacts in release mode, with optimizations.
+ * Defaults to "false" and creates a debug build.
+ * Equivalent to Cargo's `--release` option.
+ */
+ @Parameter(property = "release", defaultValue = "false")
+ private boolean release;
+
+ /**
+ * List of features to activate.
+ * If not specified, default features are activated.
+ * Equivalent to Cargo's `--features` option.
+ */
+ @Parameter(property = "features")
+ private String[] features;
+
+ /**
+ * Activate all available features.
+ * Defaults to "false".
+ * Equivalent to Cargo's `--all-features` option.
+ */
+ @Parameter(property = "all-features", defaultValue = "false")
+ private boolean allFeatures;
+
+ /**
+ * Do not activate the `default` feature.
+ * Defaults to "false".
+ * Equivalent to Cargo's `--no-default-features` option.
+ */
+ @Parameter(property = "no-default-features", defaultValue = "false")
+ private boolean noDefaultFeatures;
+
+ /**
+ * Set the verbosity level, forwarded to Cargo.
+ * Valid values are "", "-q", "-v", "-vv".
+ */
+ @Parameter(property = "verbosity")
+ private String verbosity;
+
+ /**
+ * Additional args to pass to cargo.
+ */
+ @Parameter(property = "extra-args")
+ private String[] extraArgs;
+
+ protected String getVerbosity() throws MojoExecutionException {
+ if (verbosity == null) {
+ return null;
+ }
+ switch (verbosity) {
+ case "":
+ return null;
+ case "-q":
+ case "-v":
+ case "-vv":
+ return verbosity;
+ default:
+ throw new MojoExecutionException("Invalid verbosity: " + verbosity);
+ }
+ }
+
+ protected Path getCrateRoot() {
+ Path crateRoot = Paths.get(path);
+ if (!crateRoot.isAbsolute()) {
+ crateRoot = project.getBasedir().toPath().resolve(path);
+ }
+ return crateRoot;
+ }
+
+ protected Path getTargetRootDir() {
+ return Paths.get(
+ project.getBuild().getDirectory(),
+ "rust-maven-plugin");
+ }
+
+ protected Crate.Params getCommonCrateParams() throws MojoExecutionException {
+ final Crate.Params params = new Crate.Params();
+ params.verbosity = getVerbosity();
+ params.environmentVariables = environmentVariables;
+ params.cargoPath = cargoPath;
+ params.release = release;
+ params.features = features;
+ params.allFeatures = allFeatures;
+ params.noDefaultFeatures = noDefaultFeatures;
+ params.extraArgs = extraArgs;
+ return params;
+ }
+}
diff --git a/rust-maven-plugin/src/main/java/io/questdb/maven/rust/CargoTestMojo.java b/rust-maven-plugin/src/main/java/io/questdb/maven/rust/CargoTestMojo.java
new file mode 100644
index 0000000..8ba7511
--- /dev/null
+++ b/rust-maven-plugin/src/main/java/io/questdb/maven/rust/CargoTestMojo.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * ___ _ ____ ____
+ * / _ \ _ _ ___ ___| |_| _ \| __ )
+ * | | | | | | |/ _ \/ __| __| | | | _ \
+ * | |_| | |_| | __/\__ \ |_| |_| | |_) |
+ * \__\_\\__,_|\___||___/\__|____/|____/
+ *
+ * Copyright (c) 2014-2019 Appsicle
+ * Copyright (c) 2019-2023 QuestDB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+package io.questdb.maven.rust;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+@Mojo(name = "test", defaultPhase = LifecyclePhase.TEST, threadSafe = true)
+public class CargoTestMojo extends CargoMojoBase {
+ /**
+ * Skips running tests when building with `mvn package -DskipTests=true`.
+ */
+ @Parameter(property = "skipTests", defaultValue = "false")
+ private boolean skipTests;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ if (skipTests) {
+ getLog().info("Skipping tests");
+ return;
+ }
+ final Crate crate = new Crate(
+ getCrateRoot(),
+ getTargetRootDir(),
+ getCommonCrateParams());
+ crate.setLog(getLog());
+ crate.test();
+ }
+}
diff --git a/rust-maven-plugin/src/main/java/io/questdb/maven/rust/Crate.java b/rust-maven-plugin/src/main/java/io/questdb/maven/rust/Crate.java
new file mode 100644
index 0000000..125d4f9
--- /dev/null
+++ b/rust-maven-plugin/src/main/java/io/questdb/maven/rust/Crate.java
@@ -0,0 +1,501 @@
+/*******************************************************************************
+ * ___ _ ____ ____
+ * / _ \ _ _ ___ ___| |_| _ \| __ )
+ * | | | | | | |/ _ \/ __| __| | | | _ \
+ * | |_| | |_| | __/\__ \ |_| |_| | |_) |
+ * \__\_\\__,_|\___||___/\__|____/|____/
+ *
+ * Copyright (c) 2014-2019 Appsicle
+ * Copyright (c) 2019-2023 QuestDB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ******************************************************************************/
+
+package io.questdb.maven.rust;
+
+import io.questdb.jar.jni.OsInfo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.logging.Log;
+import org.tomlj.Toml;
+import org.tomlj.TomlArray;
+import org.tomlj.TomlInvalidTypeException;
+import org.tomlj.TomlTable;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.Executors;
+
+/**
+ * Controls running tasks on a Rust crate.
+ */
+public class Crate {
+ private final Path crateRoot;
+ private final Path targetDir;
+ private final Params params;
+ private final TomlTable cargoToml;
+ private final String packageName;
+ private Log log;
+
+ public Crate(
+ Path crateRoot,
+ Path targetRootDir,
+ Params params) throws MojoExecutionException {
+ this.log = nullLog();
+ this.crateRoot = crateRoot;
+ this.targetDir = targetRootDir.resolve(getDirName());
+ this.params = params;
+
+ final Path tomlPath = crateRoot.resolve("Cargo.toml");
+ if (!Files.exists(tomlPath, LinkOption.NOFOLLOW_LINKS)) {
+ throw new MojoExecutionException(
+ "Cargo.toml file expected under: " + crateRoot);
+ }
+ try {
+ this.cargoToml = Toml.parse(tomlPath);
+ } catch (IOException e) {
+ throw new MojoExecutionException(
+ "Failed to parse Cargo.toml file: " + e.getMessage());
+ }
+
+ try {
+ packageName = cargoToml.getString("package.name");
+ if (packageName == null) {
+ throw new MojoExecutionException(
+ "Missing required `package.name` from Cargo.toml file");
+ }
+ } catch (TomlInvalidTypeException e) {
+ throw new MojoExecutionException(
+ "Failed to extract `package.name` from Cargo.toml file: " +
+ e.getMessage());
+ }
+ }
+
+ public static String pinLibName(String name) {
+ return OsInfo.LIB_PREFIX +
+ name.replace('-', '_') +
+ OsInfo.LIB_SUFFIX;
+ }
+
+ public static String pinBinName(String name) {
+ return name + OsInfo.EXE_SUFFIX;
+ }
+
+ public static Log nullLog() {
+ return new Log() {
+ @Override
+ public void debug(CharSequence content) {
+ }
+
+ @Override
+ public void debug(CharSequence content, Throwable error) {
+ }
+
+ @Override
+ public void debug(Throwable error) {
+ }
+
+ @Override
+ public void error(CharSequence content) {
+ }
+
+ @Override
+ public void error(CharSequence content, Throwable error) {
+ }
+
+ @Override
+ public void error(Throwable error) {
+ }
+
+ @Override
+ public void info(CharSequence content) {
+ }
+
+ @Override
+ public void info(CharSequence content, Throwable error) {
+ }
+
+ @Override
+ public void info(Throwable error) {
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isErrorEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isInfoEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isWarnEnabled() {
+ return false;
+ }
+
+ @Override
+ public void warn(CharSequence content) {
+ }
+
+ @Override
+ public void warn(CharSequence content, Throwable error) {
+ }
+
+ @Override
+ public void warn(Throwable error) {
+ }
+ };
+ }
+
+ public void setLog(Log log) {
+ this.log = log;
+ }
+
+ private String getDirName() {
+ return crateRoot.getFileName().toString();
+ }
+
+ private String getProfile() {
+ return params.release ? "release" : "debug";
+ }
+
+ public boolean hasCdylib() {
+ try {
+ TomlArray crateTypes = cargoToml.getArray("lib.crate-type");
+ if (crateTypes == null) {
+ return false;
+ }
+
+ for (int index = 0; index < crateTypes.size(); index++) {
+ String crateType = crateTypes.getString(index);
+ if ((crateType != null) && crateType.equals("cdylib")) {
+ return true;
+ }
+ }
+
+ return false;
+ } catch (TomlInvalidTypeException e) {
+ return false;
+ }
+ }
+
+ private String getCdylibName() throws MojoExecutionException {
+ String name = null;
+ try {
+ name = cargoToml.getString("lib.name");
+ } catch (TomlInvalidTypeException e) {
+ throw new MojoExecutionException(
+ "Failed to extract `lib.name` from Cargo.toml file: " +
+ e.getMessage());
+ }
+
+ // The name might be missing, but the lib section might be present.
+ if ((name == null) && hasCdylib()) {
+ name = packageName;
+ }
+
+ return name;
+ }
+
+ private List getBinNames() throws MojoExecutionException {
+ final List binNames = new java.util.ArrayList<>();
+
+ String defaultBin = null;
+ if (Files.exists(crateRoot.resolve("src").resolve("main.rs"))) {
+ // Expecting default bin, given that there's no lib.
+ defaultBin = packageName;
+ binNames.add(defaultBin);
+ }
+
+ TomlArray bins;
+ try {
+ bins = cargoToml.getArray("bin");
+ } catch (TomlInvalidTypeException e) {
+ throw new MojoExecutionException(
+ "Failed to extract `bin`s from Cargo.toml file: " +
+ e.getMessage());
+ }
+
+ if (bins == null) {
+ return binNames;
+ }
+
+ for (int index = 0; index < bins.size(); ++index) {
+ final TomlTable bin = bins.getTable(index);
+ if (bin == null) {
+ throw new MojoExecutionException(
+ "Failed to extract `bin`s from Cargo.toml file: " +
+ "expected a `bin` table at index " + index);
+ }
+
+ String name = null;
+ try {
+ name = bin.getString("name");
+ } catch (TomlInvalidTypeException e) {
+ throw new MojoExecutionException(
+ "Failed to extract `bin`s from Cargo.toml file: " +
+ "expected a string at index " + index + " `name` key");
+ }
+
+ if (name == null) {
+ throw new MojoExecutionException(
+ "Failed to extract `bin`s from Cargo.toml file: " +
+ "missing `name` key at `bin` with index " + index);
+ }
+
+ String path = null;
+ try {
+ path = bin.getString("path");
+ } catch (TomlInvalidTypeException e) {
+ throw new MojoExecutionException(
+ "Failed to extract `bin`s from Cargo.toml file: " +
+ "expected a string at index " + index + " `path` key");
+ }
+
+ // Handle special case where the default bin is renamed.
+ if ((path != null) && path.equals("src/main.rs")) {
+ defaultBin = name;
+ binNames.remove(0);
+ binNames.add(0, defaultBin);
+ }
+
+ // This `[[bin]]` entry just configures the default bin.
+ // It's already been added.
+ if (!name.equals(defaultBin)) {
+ binNames.add(name);
+ }
+ }
+
+ return binNames;
+ }
+
+ public List getArtifactPaths() throws MojoExecutionException {
+ List paths = new ArrayList<>();
+ final String profile = getProfile();
+
+ final String libName = getCdylibName();
+ if (libName != null) {
+ final Path libPath = targetDir
+ .resolve(profile)
+ .resolve(pinLibName(libName));
+ paths.add(libPath);
+ }
+
+ for (String binName : getBinNames()) {
+ final Path binPath = targetDir
+ .resolve(profile)
+ .resolve(pinBinName(binName));
+ paths.add(binPath);
+ }
+
+ return paths;
+ }
+
+ private String getCargoPath() {
+ String path = params.cargoPath;
+
+ final boolean isWindows = System.getProperty("os.name")
+ .toLowerCase().startsWith("windows");
+
+ // Expand "~" to user's home directory.
+ // This works around a limitation of ProcessBuilder.
+ if (!isWindows && path.startsWith("~/")) {
+ path = System.getProperty("user.home") + path.substring(1);
+ }
+
+ return path;
+ }
+
+ private void runCommand(List args)
+ throws IOException, InterruptedException, MojoExecutionException {
+ final ProcessBuilder processBuilder = new ProcessBuilder(args);
+ processBuilder.redirectErrorStream(true);
+ processBuilder.environment().putAll(params.environmentVariables);
+
+ // Set the current working directory for the cargo command.
+ processBuilder.directory(crateRoot.toFile());
+ final Process process = processBuilder.start();
+ Executors.newSingleThreadExecutor().submit(() -> {
+ new BufferedReader(new InputStreamReader(process.getInputStream()))
+ .lines()
+ .forEach(log::info);
+ });
+
+ final int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ throw new MojoExecutionException(
+ "Cargo command failed with exit code " + exitCode);
+ }
+ }
+
+ private void cargo(List args) throws MojoExecutionException, MojoFailureException {
+ String cargoPath = getCargoPath();
+ final List cmd = new ArrayList<>();
+ cmd.add(cargoPath);
+ cmd.addAll(args);
+ log.info("Working directory: " + crateRoot);
+ if (!params.environmentVariables.isEmpty()) {
+ log.info("Environment variables:");
+ for (String key : params.environmentVariables.keySet()) {
+ log.info(" " + key + "=" + Shlex.quote(
+ params.environmentVariables.get(key)));
+ }
+ }
+ log.info("Running: " + Shlex.quote(cmd));
+ try {
+ runCommand(cmd);
+ } catch (IOException | InterruptedException e) {
+ CargoInstalledChecker.INSTANCE.check(log, cargoPath);
+ throw new MojoFailureException("Failed to invoke cargo", e);
+ }
+ }
+
+ private void addCargoArgs(List args) {
+ if (params.verbosity != null) {
+ args.add(params.verbosity);
+ }
+
+ args.add("--target-dir");
+ args.add(targetDir.toAbsolutePath().toString());
+
+ if (params.release) {
+ args.add("--release");
+ }
+
+ if (params.allFeatures) {
+ args.add("--all-features");
+ }
+
+ if (params.noDefaultFeatures) {
+ args.add("--no-default-features");
+ }
+
+ if (params.features != null && params.features.length > 0) {
+ args.add("--features");
+ args.add(String.join(",", params.features));
+ }
+
+ if (params.tests) {
+ args.add("--tests");
+ }
+
+ if (params.extraArgs != null) {
+ Collections.addAll(args, params.extraArgs);
+ }
+ }
+
+ public void build() throws MojoExecutionException, MojoFailureException {
+ List args = new ArrayList<>();
+ args.add("build");
+ addCargoArgs(args);
+ cargo(args);
+ }
+
+ public void test() throws MojoExecutionException, MojoFailureException {
+ List args = new ArrayList<>();
+ args.add("test");
+ addCargoArgs(args);
+ cargo(args);
+ }
+
+ private Path resolveCopyToDir() throws MojoExecutionException {
+
+ Path copyToDir = params.copyToDir;
+
+ if (copyToDir == null) {
+ return null;
+ }
+
+ if (params.copyWithPlatformDir) {
+ copyToDir = copyToDir.resolve(OsInfo.PLATFORM);
+ }
+
+ if (!Files.exists(copyToDir, LinkOption.NOFOLLOW_LINKS)) {
+ try {
+ Files.createDirectories(copyToDir);
+ } catch (IOException e) {
+ throw new MojoExecutionException(
+ "Failed to create directory " + copyToDir +
+ ": " + e.getMessage(), e);
+ }
+ }
+
+ if (!Files.isDirectory(copyToDir)) {
+ throw new MojoExecutionException(copyToDir + " is not a directory");
+ }
+ return copyToDir;
+ }
+
+ public void copyArtifacts() throws MojoExecutionException {
+ // Cargo nightly has support for `--out-dir`
+ // which allows us to copy the artifacts directly to the desired path.
+ // Once the feature is stabilized, copy the artifacts directly via:
+ // args.add("--out-dir")
+ // args.add(resolveCopyToDir());
+ final Path copyToDir = resolveCopyToDir();
+ if (copyToDir == null) {
+ return;
+ }
+ final List artifactPaths = getArtifactPaths();
+ log.info(
+ "Copying " + getDirName() +
+ "'s artifacts to " + Shlex.quote(
+ copyToDir.toAbsolutePath().toString()));
+
+ for (Path artifactPath : artifactPaths) {
+ final Path fileName = artifactPath.getFileName();
+ final Path destPath = copyToDir.resolve(fileName);
+ try {
+ Files.copy(
+ artifactPath,
+ destPath,
+ StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ throw new MojoExecutionException(
+ "Failed to copy " + artifactPath +
+ " to " + copyToDir + ":" + e.getMessage());
+ }
+ log.info("Copied " + Shlex.quote(fileName.toString()));
+ }
+ }
+
+ public static class Params {
+ public String verbosity;
+ public HashMap environmentVariables;
+ public String cargoPath;
+ public boolean release;
+ public String[] features;
+ public boolean allFeatures;
+ public boolean noDefaultFeatures;
+ public boolean tests;
+ public String[] extraArgs;
+ public Path copyToDir;
+ public boolean copyWithPlatformDir;
+ }
+}
diff --git a/rust-maven-plugin/src/main/java/io/questdb/maven/rust/ManualOutputRedirector.java b/rust-maven-plugin/src/main/java/io/questdb/maven/rust/ManualOutputRedirector.java
deleted file mode 100644
index 9f9a6f7..0000000
--- a/rust-maven-plugin/src/main/java/io/questdb/maven/rust/ManualOutputRedirector.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*******************************************************************************
- * ___ _ ____ ____
- * / _ \ _ _ ___ ___| |_| _ \| __ )
- * | | | | | | |/ _ \/ __| __| | | | _ \
- * | |_| | |_| | __/\__ \ |_| |_| | |_) |
- * \__\_\\__,_|\___||___/\__|____/|____/
- *
- * Copyright (c) 2014-2019 Appsicle
- * Copyright (c) 2019-2023 QuestDB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- ******************************************************************************/
-
-package io.questdb.maven.rust;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.StandardCopyOption;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.apache.maven.plugin.MojoExecutionException;
-import org.apache.maven.plugin.logging.Log;
-
-import com.moandjiezana.toml.Toml;
-
-final class ManualOutputRedirector {
-
- private final Log log;
- private final String name;
- private final boolean release;
- private final File path;
- private final File targetDir;
- private final File copyToDir;
-
- ManualOutputRedirector(Log log, String name, boolean release, File path, File targetDir, File copyToDir) {
- this.log = log;
- this.name = name;
- this.release = release;
- this.path = path;
- this.targetDir = targetDir;
- this.copyToDir = copyToDir;
- }
-
- void copyArtifacts() throws MojoExecutionException {
- if (copyToDir == null) {
- log.info("Not copying artifacts is not set");
- return;
- }
-
- log.info("Copying " + name + "'s artifacts to " + Shlex.quote(copyToDir.getAbsolutePath()));
-
- final File tomlFile = new File(path, "Cargo.toml");
- if (!tomlFile.exists()) {
- throw new MojoExecutionException("The arg might be incorrect. Cargo.toml file expected under: " + path);
- }
- final Toml toml = new Toml().read(tomlFile);
-
- final Set artifacts = getArtifacts(toml);
- for (File artifact : artifacts) {
- final File copyToPath = new File(copyToDir, artifact.getName());
- try {
- Files.copy(artifact.toPath(), copyToPath.toPath(), StandardCopyOption.REPLACE_EXISTING);
- } catch (IOException e) {
- throw new MojoExecutionException("Failed to copy " + artifact + " to " + copyToPath + ":" + e.getMessage());
- }
- log.info("Copied " + Shlex.quote(artifact.getName()));
- }
- }
-
- Set getArtifacts(Toml toml) throws MojoExecutionException {
- final String buildType = release ? "release" : "debug";
-
- Toml mainPackage = toml.getTable("package");
- if (mainPackage == null) {
- throw new MojoExecutionException("Malformed Cargo.toml file, expected a [package] section, but was missing.");
- }
- String packageName = mainPackage.getString("name", name);
-
- final Set artifacts = new HashSet<>();
-
- // lib.rs or explicit [lib]
- final File libraryPath = getLibraryPath(toml, packageName, buildType);
- if (libraryPath != null) {
- artifacts.add(libraryPath);
- }
-
- // main.rs
- final File defaultBinPath = new File(targetDir, buildType + "/" + packageName);
- if (defaultBinPath.exists()) {
- artifacts.add(defaultBinPath);
- }
-
- // other [[bin]]
- final List bins = toml.getTables("bin");
- if (bins != null) {
- for (Toml bin : bins) {
- final String binName = bin.getString("name");
- if (binName != null) {
- final File binPath = new File(targetDir, buildType + "/" + binName);
- if (binPath.exists()) {
- artifacts.add(binPath);
- } else {
- throw new MojoExecutionException("Could not find expected binary: " + Shlex.quote(binPath.getAbsolutePath()));
- }
- } else {
- throw new MojoExecutionException("Malformed Cargo.toml file, missing name in [[bin]] section");
- }
- }
- }
-
- if (artifacts.isEmpty()) {
- throw new MojoExecutionException("Something went wrong. No artifacts produced. We expect a main.rs or specified [lib] or [[bin]] sections");
- }
-
- return artifacts;
- }
-
- private File getLibraryPath(Toml toml, String packageName, String buildType) throws MojoExecutionException {
- final Toml libSection = toml.getTable("lib");
-
- // TODO:
- // For now we only support system dynamic libraries (crate_type = cdylib).
- // To make this more general, we need to parse the create_type from the .toml and check for --crate-type extra args.
- // This gets even more complicated when you consider the dynamic create_types like: crate_type = "lib"
- // Hopefully, --out-dir will allow us to avoid all this complexity in the future
- final String libraryName = getOSspecificLibraryName(
- libSection == null ? packageName : libSection.getString("name", packageName));
- final File libraryPath = new File(targetDir, buildType + "/" + libraryName);
-
- if (libraryPath.exists()) {
- return libraryPath;
- } else {
- if (libSection == null) {
- // assume no [lib] or lib.rs present, which is fine assuming this is a binary only crate
- return null;
- } else {
- // if [lib] section is present then we expect library to be produced
- throw new MojoExecutionException("Could not find expected library: " + Shlex.quote(libraryPath.getAbsolutePath()));
- }
- }
- }
-
- static String getOSspecificLibraryName(String libName) {
- final String osName = System.getProperty("os.name").toLowerCase();
- final String libPrefix = osName.startsWith("windows") ? "" : "lib";
- final String libSuffix = osName.startsWith("windows")
- ? ".dll" : osName.contains("mac")
- ? ".dylib" : ".so";
- return libPrefix + libName.replace("-", "_") + libSuffix;
- }
-}
diff --git a/rust-maven-plugin/src/test/java/io/questdb/maven/rust/CrateTest.java b/rust-maven-plugin/src/test/java/io/questdb/maven/rust/CrateTest.java
new file mode 100644
index 0000000..dbe9f41
--- /dev/null
+++ b/rust-maven-plugin/src/test/java/io/questdb/maven/rust/CrateTest.java
@@ -0,0 +1,511 @@
+package io.questdb.maven.rust;
+
+import io.questdb.jar.jni.OsInfo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+
+public class CrateTest {
+
+ @Rule
+ public TemporaryFolder tmpDir = new TemporaryFolder();
+ private Path targetRootDir;
+
+ public static boolean isWindows() {
+ return System.getProperty("os.name").toLowerCase().contains("win");
+ }
+
+ public static boolean isMac() {
+ return System.getProperty("os.name").toLowerCase().contains("mac");
+ }
+
+ @Before
+ public void before() throws IOException {
+ tmpDir = new TemporaryFolder();
+ tmpDir.create();
+ targetRootDir = tmpDir.newFolder(
+ "target",
+ "rust-maven-plugin").toPath();
+ }
+
+ @After
+ public void after() {
+ tmpDir.delete();
+ }
+
+ private void doTestDefaultBin(
+ boolean release,
+ boolean copyTo,
+ boolean copyWithPlatformDir) throws Exception {
+ // Setting up mock Rust project directory.
+ final MockCrate mock = new MockCrate(
+ "test-bin-1",
+ release ? "release" : "debug");
+ mock.writeCargoToml(
+ "[package]\n" +
+ "name = \"test-bin\"\n" +
+ "version = \"0.1.0\"\n" +
+ "edition = \"2021\"\n");
+ mock.touchSrc("main.rs");
+ final Path mockBinPath = mock.touchBin("test-bin");
+ assertTrue(Files.exists(mockBinPath));
+
+ // Configuring the build job.
+ final Crate.Params params = new Crate.Params();
+ params.release = release;
+ if (copyTo) {
+ params.copyToDir = tmpDir.newFolder("bin_dest_dir").toPath();
+ }
+ params.copyWithPlatformDir = copyWithPlatformDir;
+
+ final Crate crate = new Crate(
+ mock.crateRoot,
+ targetRootDir,
+ params);
+
+ // Checking the expected paths it generates.
+ final List artifacts = crate.getArtifactPaths();
+ assertEquals(1, artifacts.size());
+ assertEquals(mockBinPath, artifacts.get(0));
+
+ if (!copyTo) {
+ return;
+ }
+
+ crate.copyArtifacts();
+
+ Path expectedBinPath = params.copyToDir;
+ if (copyWithPlatformDir) {
+ expectedBinPath = expectedBinPath.resolve(
+ OsInfo.PLATFORM);
+ }
+ expectedBinPath = expectedBinPath.resolve(mockBinPath.getFileName());
+
+ assertTrue(Files.exists(expectedBinPath));
+ }
+
+ public void testDefaultBinDebugNoCopyTo() throws Exception {
+ doTestDefaultBin(false, false, false);
+ }
+
+ public void testDefaultBinReleaseNoCopyTo() throws Exception {
+ // Last arg to `true` should be ignored.
+ doTestDefaultBin(true, false, true);
+ }
+
+ @Test
+ public void testDefaultBinDebugCopyTo() throws Exception {
+ doTestDefaultBin(false, true, false);
+ }
+
+ @Test
+ public void testDefaultBinReleaseCopyTo() throws Exception {
+ doTestDefaultBin(true, true, false);
+ }
+
+ @Test
+ public void testDefaultBinDebugCopyToPlatformDir() throws Exception {
+ doTestDefaultBin(false, true, true);
+ }
+
+ @Test
+ public void testDefaultBinReleaseCopyToPlatformDir() throws Exception {
+ doTestDefaultBin(true, true, true);
+ }
+
+ private void doTestCdylib(boolean release, boolean copyTo, boolean copyWithPlatformDir) throws Exception {
+ // Setting up mock Rust project directory.
+ final MockCrate mock = new MockCrate(
+ "test-lib-1",
+ release ? "release" : "debug");
+ mock.writeCargoToml(
+ "[package]\n" +
+ "name = \"test-lib\"\n" +
+ "version = \"0.1.0\"\n" +
+ "edition = \"2021\"\n" +
+ "\n" +
+ "[lib]\n" +
+ "crate-type = [\"cdylib\"]\n");
+ mock.touchSrc("lib.rs");
+ final Path cdylibPath = mock.touchLib("test-lib");
+
+ // Configuring the build job.
+ final Crate.Params params = new Crate.Params();
+ params.release = release;
+ if (copyTo) {
+ params.copyToDir = tmpDir.newFolder("lib_dest_dir").toPath();
+ }
+ params.copyWithPlatformDir = copyWithPlatformDir;
+
+ final Crate crate = new Crate(
+ mock.crateRoot,
+ targetRootDir,
+ params);
+
+ // Checking the expected paths it generates.
+ final List artifacts = crate.getArtifactPaths();
+ assertEquals(1, artifacts.size());
+ assertEquals(cdylibPath, artifacts.get(0));
+
+ if (!copyTo) {
+ return;
+ }
+
+ crate.copyArtifacts();
+
+ Path expectedLibPath = params.copyToDir;
+ if (copyWithPlatformDir) {
+ expectedLibPath = expectedLibPath.resolve(OsInfo.PLATFORM);
+ }
+ expectedLibPath = expectedLibPath.resolve(cdylibPath.getFileName());
+
+ assertTrue(Files.exists(expectedLibPath));
+ }
+
+ @Test
+ public void testCdylibDebug() throws Exception {
+ // Last arg to `true` should be ignored.
+ doTestCdylib(false, false, true);
+ }
+
+ @Test
+ public void testCdylibDebugCopyTo() throws Exception {
+ doTestCdylib(false, true, false);
+ }
+
+ @Test
+ public void testCdylibReleaseCopyTo() throws Exception {
+ doTestCdylib(true, true, false);
+ }
+
+ @Test
+ public void testCdylibDebugCopyToPlatformDir() throws Exception {
+ doTestCdylib(false, true, true);
+ }
+
+ @Test
+ public void testCdylibReleaseCopyToPlatformDir() throws Exception {
+ doTestCdylib(true, true, true);
+ }
+
+ @Test
+ public void testCustomCdylibName() throws Exception {
+ // Setting up mock Rust project directory.
+ final MockCrate mock = new MockCrate(
+ "test-lib-1",
+ "debug");
+ mock.writeCargoToml(
+ "[package]\n" +
+ "name = \"test-lib\"\n" +
+ "version = \"0.1.0\"\n" +
+ "edition = \"2021\"\n" +
+ "\n" +
+ "[lib]\n" +
+ "name = \"mylib\"\n" +
+ "crate-type = [\"cdylib\"]\n");
+ mock.touchSrc("lib.rs");
+ final Path cdylibPath = mock.touchLib("mylib");
+
+ // Configuring the build job.
+ final Crate.Params params = new Crate.Params();
+ params.release = false;
+ params.copyToDir = tmpDir.newFolder("lib_dest_dir").toPath();
+ params.copyWithPlatformDir = true;
+
+ final Crate crate = new Crate(
+ mock.crateRoot,
+ targetRootDir,
+ params);
+
+ // Checking the expected paths it generates.
+ final List artifacts = crate.getArtifactPaths();
+ assertEquals(1, artifacts.size());
+ assertEquals(cdylibPath, artifacts.get(0));
+
+ crate.copyArtifacts();
+
+ Path expectedLibPath = params.copyToDir
+ .resolve(OsInfo.PLATFORM)
+ .resolve(cdylibPath.getFileName());
+
+ assertTrue(Files.exists(expectedLibPath));
+ }
+
+ @Test
+ public void testDefaultBinAndCdylib() throws Exception {
+ // Setting up mock Rust project directory.
+ final MockCrate mock = new MockCrate(
+ "test42",
+ "debug");
+ mock.writeCargoToml(
+ "[package]\n" +
+ "name = \"test42\"\n" +
+ "version = \"0.1.0\"\n" +
+ "edition = \"2021\"\n" +
+ "\n" +
+ "[lib]\n" +
+ "crate-type = [\"cdylib\"]\n");
+ mock.touchSrc("lib.rs");
+ mock.touchSrc("main.rs");
+ final Path cdylibPath = mock.touchLib("test42");
+ final Path binPath = mock.touchBin("test42");
+
+ // Configuring the build job.
+ final Crate.Params params = new Crate.Params();
+ params.release = false;
+ params.copyToDir = tmpDir.newFolder("dest_dir").toPath();
+
+ final Crate crate = new Crate(
+ mock.crateRoot,
+ targetRootDir,
+ params);
+
+ // Checking the expected paths it generates.
+ final List artifacts = crate.getArtifactPaths();
+ assertEquals(2, artifacts.size());
+ assertEquals(cdylibPath, artifacts.get(0));
+ assertEquals(binPath, artifacts.get(1));
+
+ crate.copyArtifacts();
+
+ Path expectedLibPath = params.copyToDir
+ .resolve(cdylibPath.getFileName());
+ Path expectedBinPath = params.copyToDir
+ .resolve(binPath.getFileName());
+
+ assertTrue(Files.exists(expectedLibPath));
+ assertTrue(Files.exists(expectedBinPath));
+ }
+
+ @Test
+ public void testRenamedDefaultBin() throws Exception {
+ // Setting up mock Rust project directory.
+ final MockCrate mock = new MockCrate(
+ "test-custom-name-bin",
+ "debug");
+ mock.writeCargoToml(
+ "[package]\n" +
+ "name = \"test-custom-name-bin\"\n" +
+ "version = \"0.1.0\"\n" +
+ "edition = \"2021\"\n" +
+ "\n" +
+ "[[bin]]\n" +
+ "name = \"test43\"\n" +
+ "path = \"src/main.rs\"\n");
+ mock.touchSrc("main.rs");
+ final Path binPath = mock.touchBin("test43");
+
+ // Configuring the build job.
+ final Crate.Params params = new Crate.Params();
+ params.release = false;
+ params.copyToDir = tmpDir.newFolder("dest_dir").toPath();
+
+ final Crate crate = new Crate(
+ mock.crateRoot,
+ targetRootDir,
+ params);
+
+ // Checking the expected paths it generates.
+ final List artifacts = crate.getArtifactPaths();
+ assertEquals(1, artifacts.size());
+ assertEquals(binPath, artifacts.get(0));
+
+ crate.copyArtifacts();
+
+ Path expectedBinPath = params.copyToDir
+ .resolve(binPath.getFileName());
+
+ assertTrue(Files.exists(expectedBinPath));
+ }
+
+ @Test
+ public void testConfiguredDefaultBin() throws Exception {
+ // Setting up mock Rust project directory.
+ final MockCrate mock = new MockCrate(
+ "test-configured-bin",
+ "debug");
+ mock.writeCargoToml(
+ "[package]\n" +
+ "name = \"test-configured-bin\"\n" +
+ "version = \"0.1.0\"\n" +
+ "edition = \"2021\"\n" +
+ "\n" +
+ "[[bin]]\n" +
+ "name = \"test-configured-bin\"\n" +
+ "path = \"src/main.rs\"\n");
+ mock.touchSrc("main.rs");
+ final Path binPath = mock.touchBin("test-configured-bin");
+
+ // Configuring the build job.
+ final Crate.Params params = new Crate.Params();
+ params.release = false;
+ params.copyToDir = tmpDir.newFolder("dest_dir").toPath();
+
+ final Crate crate = new Crate(
+ mock.crateRoot,
+ targetRootDir,
+ params);
+
+ // Checking the expected paths it generates.
+ final List artifacts = crate.getArtifactPaths();
+ assertEquals(1, artifacts.size());
+ assertEquals(binPath, artifacts.get(0));
+
+ crate.copyArtifacts();
+
+ Path expectedBinPath = params.copyToDir
+ .resolve(binPath.getFileName());
+
+ assertTrue(Files.exists(expectedBinPath));
+ }
+
+ @Test
+ public void testCdylibDefaultBinAndExplicitBin() throws Exception {
+ // Setting up mock Rust project directory.
+ final MockCrate mock = new MockCrate("mixed", "release");
+ mock.writeCargoToml(
+ "[package]\n" +
+ "name = \"mixed\"\n" +
+ "version = \"0.1.0\"\n" +
+ "edition = \"2021\"\n" +
+ "\n" +
+ "[lib]\n" +
+ "crate-type = [\"cdylib\"]\n" +
+ "\n" +
+ "[[bin]]\n" +
+ "name = \"extra-bin\"\n" +
+ "path = \"src/extra-bin/main.rs\"\n");
+ mock.touchSrc("lib.rs");
+ mock.touchSrc("main.rs");
+ mock.touchSrc("extra-bin", "main.rs");
+
+ final Path cdylibPath = mock.touchLib("mixed");
+ final Path binPath = mock.touchBin("mixed");
+ final Path extraBinPath = mock.touchBin("extra-bin");
+
+ // Configuring the build job.
+ final Crate.Params params = new Crate.Params();
+ params.release = true;
+ params.copyToDir = tmpDir.newFolder("dest_dir").toPath();
+
+ final Crate crate = new Crate(
+ mock.crateRoot,
+ targetRootDir,
+ params);
+
+ // Checking the expected paths it generates.
+ final List artifacts = crate.getArtifactPaths();
+ assertEquals(3, artifacts.size());
+ assertEquals(cdylibPath, artifacts.get(0));
+ assertEquals(binPath, artifacts.get(1));
+ assertEquals(extraBinPath, artifacts.get(2));
+
+ crate.copyArtifacts();
+
+ Path expectedLibPath = params.copyToDir
+ .resolve(cdylibPath.getFileName());
+ Path expectedBinPath = params.copyToDir
+ .resolve(binPath.getFileName());
+ Path expectedExtraBinPath = params.copyToDir
+ .resolve(extraBinPath.getFileName());
+
+ assertTrue(Files.exists(expectedLibPath));
+ assertTrue(Files.exists(expectedBinPath));
+ assertTrue(Files.exists(expectedExtraBinPath));
+ }
+
+ @Test
+ public void testBadCargoToml() throws Exception {
+ // Setting up mock Rust project directory.
+ final MockCrate mock = new MockCrate("bad-toml", "release");
+ mock.writeCargoToml(
+ // "[package]\n" + MISSING!
+ "name = \"bad-toml\"\n" +
+ "version = \"0.1.0\"\n" +
+ "edition = \"2021\"\n");
+ mock.touchSrc("main.rs");
+ mock.touchBin("bad-toml");
+
+ // Configuring the build job.
+ final Crate.Params params = new Crate.Params();
+ params.release = true;
+ params.copyToDir = tmpDir.newFolder("dest_dir").toPath();
+
+ assertThrows(
+ MojoExecutionException.class,
+ () -> new Crate(mock.crateRoot, targetRootDir, params));
+ }
+
+ class MockCrate {
+ private final String name;
+ private final String profile;
+ private final Path crateRoot;
+
+ public MockCrate(String name, String profile) throws IOException {
+ this.name = name;
+ this.profile = profile;
+ this.crateRoot = tmpDir.newFolder(name).toPath();
+ }
+
+ public void writeCargoToml(String contents) throws IOException {
+ Path cargoToml = crateRoot.resolve("Cargo.toml");
+ try (PrintWriter w = new PrintWriter(cargoToml.toFile(), "UTF-8")) {
+ w.write(contents);
+ }
+ }
+
+ public Path touchBin(String name) throws IOException {
+ final Path mockBinPath = targetRootDir
+ .resolve(this.name)
+ .resolve(profile)
+ .resolve(name + (isWindows() ? ".exe" : ""));
+ if (!Files.exists(mockBinPath.getParent())) {
+ Files.createDirectories(mockBinPath.getParent());
+ }
+ Files.createFile(mockBinPath);
+ return mockBinPath;
+ }
+
+ public Path touchSrc(String... pathComponents) throws IOException {
+ Path srcPath = crateRoot.resolve("src");
+ for (String pathComponent : pathComponents) {
+ srcPath = srcPath.resolve(pathComponent);
+ }
+ if (!Files.exists(srcPath.getParent())) {
+ Files.createDirectories(srcPath.getParent());
+ }
+ Files.createFile(srcPath);
+ return srcPath;
+ }
+
+ public Path touchLib(String name) throws IOException {
+ final String prefix = isWindows() ? "" : "lib";
+ final String suffix =
+ isWindows() ? ".dll" :
+ isMac() ? ".dylib"
+ : ".so";
+ final String libName = prefix + name.replace('-', '_') + suffix;
+ final Path libPath = targetRootDir
+ .resolve(this.name)
+ .resolve(profile)
+ .resolve(libName);
+ if (!Files.exists(libPath.getParent())) {
+ Files.createDirectories(libPath.getParent());
+ }
+ Files.createFile(libPath);
+ return libPath;
+ }
+ }
+}
diff --git a/rust-maven-plugin/src/test/java/io/questdb/maven/rust/ManualOutputRedirectorTest.java b/rust-maven-plugin/src/test/java/io/questdb/maven/rust/ManualOutputRedirectorTest.java
deleted file mode 100644
index c912d75..0000000
--- a/rust-maven-plugin/src/test/java/io/questdb/maven/rust/ManualOutputRedirectorTest.java
+++ /dev/null
@@ -1,194 +0,0 @@
-package io.questdb.maven.rust;
-
-import static org.junit.Assert.*;
-
-import java.io.File;
-import java.io.IOException;
-
-import org.apache.maven.plugin.MojoExecutionException;
-import org.apache.maven.plugin.logging.SystemStreamLog;
-import org.codehaus.plexus.util.FileUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import com.google.common.collect.Sets;
-import com.moandjiezana.toml.Toml;
-
-public class ManualOutputRedirectorTest {
-
- @Rule
- public TemporaryFolder tempTargetDir = new TemporaryFolder();
-
- @Before
- public void before() throws IOException {
- tempTargetDir.newFolder("debug");
- }
-
- @Test
- public void testFindDefaultBin() throws Exception {
- ManualOutputRedirector testObject = new ManualOutputRedirector(null, "dummy", false, null, tempTargetDir.getRoot(), null);
-
- Toml toml = new Toml().read("[package]\n"
- + "name = \"test\"\n"
- + "version = \"0.1.0\"\n"
- + "edition = \"2021\"");
-
- assertThrows(MojoExecutionException.class, () -> testObject.getArtifacts(toml));
-
- File expected = tempTargetDir.newFile("debug/test");
-
- assertEquals(Sets.newHashSet(expected), testObject.getArtifacts(toml));
- }
-
- @Test
- public void testFindDefaultLib() throws Exception {
- ManualOutputRedirector testObject = new ManualOutputRedirector(null, "dummy", false, null, tempTargetDir.getRoot(), null);
-
- Toml toml = new Toml().read("[package]\n"
- + "name = \"test\"\n"
- + "version = \"0.1.0\"\n"
- + "edition = \"2021\"");
-
- assertThrows(MojoExecutionException.class, () -> testObject.getArtifacts(toml));
-
- File expected = tempTargetDir.newFile("debug/" + ManualOutputRedirector.getOSspecificLibraryName("test"));
-
- assertEquals(Sets.newHashSet(expected), testObject.getArtifacts(toml));
- }
-
- @Test
- public void testFindDefaultLibWithName() throws Exception {
- ManualOutputRedirector testObject = new ManualOutputRedirector(null, "dummy", false, null, tempTargetDir.getRoot(), null);
-
- Toml toml = new Toml().read("[package]\n"
- + "name = \"test\"\n"
- + "version = \"0.1.0\"\n"
- + "edition = \"2021\"\n"
- + "[lib]\n"
- + "name = \"mylib\"");
-
- assertThrows(MojoExecutionException.class, () -> testObject.getArtifacts(toml));
-
- File expected = tempTargetDir.newFile("debug/" + ManualOutputRedirector.getOSspecificLibraryName("mylib"));
-
- assertEquals(Sets.newHashSet(expected), testObject.getArtifacts(toml));
- }
-
- @Test
- public void testFindDefaultBinAndLibrary() throws Exception {
- ManualOutputRedirector testObject = new ManualOutputRedirector(null, "dummy", false, null, tempTargetDir.getRoot(), null);
-
- Toml toml = new Toml().read("[package]\n"
- + "name = \"test\"\n"
- + "version = \"0.1.0\"\n"
- + "edition = \"2021\"");
-
- File expectedBin = tempTargetDir.newFile("debug/test");
- File expectedLib = tempTargetDir.newFile("debug/" + ManualOutputRedirector.getOSspecificLibraryName("test"));
-
- assertEquals(Sets.newHashSet(expectedLib, expectedBin), testObject.getArtifacts(toml));
- }
-
- @Test
- public void testFindDefaultBinExplicit() throws Exception {
- ManualOutputRedirector testObject = new ManualOutputRedirector(null, "dummy", false, null, tempTargetDir.getRoot(), null);
-
- Toml toml = new Toml().read("[package]\n"
- + "name = \"test\"\n"
- + "version = \"0.1.0\"\n"
- + "edition = \"2021\"\n"
- + "[[bin]]\n"
- + "name = \"test\"\n"
- + "src = \"src/main.rs\"");
-
- assertThrows(MojoExecutionException.class, () -> testObject.getArtifacts(toml));
-
- File expected = tempTargetDir.newFile("debug/test");
-
- assertEquals(Sets.newHashSet(expected), testObject.getArtifacts(toml));
- }
-
- @Test
- public void testFindMixed() throws Exception {
- ManualOutputRedirector testObject = new ManualOutputRedirector(null, "dummy", false, null, tempTargetDir.getRoot(), null);
-
- Toml toml = new Toml().read("[package]\n"
- + "name = \"test\"\n"
- + "version = \"0.1.0\"\n"
- + "edition = \"2021\"\n"
- + "[lib]\n"
- + "name = \"mylib\"\n"
- + "[[bin]]\n"
- + "name = \"myexe1\"\n"
- + "src = \"src/myexe1.rs\"\n"
- + "[[bin]]\n"
- + "name = \"myexe2\"\n"
- + "src = \"src/myexe1.rs\"");
-
- assertThrows(MojoExecutionException.class, () -> testObject.getArtifacts(toml));
-
- File expectedLib = tempTargetDir.newFile("debug/" + ManualOutputRedirector.getOSspecificLibraryName("mylib"));
- File expectedDefaultBin = tempTargetDir.newFile("debug/test");
- File expectedBin1 = tempTargetDir.newFile("debug/myexe1");
- File expectedBin2 = tempTargetDir.newFile("debug/myexe2");
-
- // do not include default lib if [lib] section specified
- tempTargetDir.newFile("debug/" + ManualOutputRedirector.getOSspecificLibraryName("test"));
-
- assertEquals(Sets.newHashSet(expectedLib, expectedDefaultBin, expectedBin1, expectedBin2), testObject.getArtifacts(toml));
- }
-
- @Test
- public void testFindBadToml() throws Exception {
- ManualOutputRedirector testObject = new ManualOutputRedirector(null, "dummy", false, null, tempTargetDir.getRoot(), null);
-
- // this should fail upstream, but for completeness test the bad Cargo.toml edge cases
-
- // missing [package]
- Toml toml1 = new Toml().read("name = \"test\"\n"
- + "version = \"0.1.0\"\n"
- + "edition = \"2021\"");
-
- tempTargetDir.newFile("debug/test");
- assertThrows(MojoExecutionException.class, () -> testObject.getArtifacts(toml1));
-
- // missing name in [[bin]] section
- Toml toml2 = new Toml().read("[package]\n"
- + "name = \"test\"\n"
- + "version = \"0.1.0\"\n"
- + "edition = \"2021\"\n"
- + "[[bin]]\n"
- + "src = \"src/myexe.rs\"");
-
- tempTargetDir.newFile("debug/myexe");
- assertThrows(MojoExecutionException.class, () -> testObject.getArtifacts(toml2));
- }
-
- @Test
- public void testCopyArtifacts() throws Exception {
- File copyToDir = tempTargetDir.newFolder("output");
-
- ManualOutputRedirector testObject = new ManualOutputRedirector(new SystemStreamLog(), "dummy", true, tempTargetDir.getRoot(), tempTargetDir.getRoot(), copyToDir);
-
- File tomlFile = new File(tempTargetDir.getRoot(), "Cargo.toml");
- FileUtils.fileWrite(tomlFile, "[package]\n"
- + "name = \"test\"\n"
- + "version = \"0.1.0\"\n"
- + "edition = \"2021\"");
-
- tempTargetDir.newFolder("release");
- File bin = tempTargetDir.newFile("release/test");
-
- File expectedBin = new File(copyToDir, bin.getName());
-
- assertFalse(expectedBin.exists());
-
- testObject.copyArtifacts();
-
- assertEquals(1, copyToDir.listFiles().length);
- assertEquals(expectedBin, copyToDir.listFiles()[0]);
- assertTrue(expectedBin.exists());
- }
-}