diff --git a/src/dotnet/Cljr/Cljr.csproj b/src/dotnet/Cljr/Cljr.csproj
index 3e25e7e..81a944f 100644
--- a/src/dotnet/Cljr/Cljr.csproj
+++ b/src/dotnet/Cljr/Cljr.csproj
@@ -1,141 +1,42 @@
-
- Exe
- net6.0;net8.0
- enable
- enable
- true
- cljr
- true
- ./nupkg
- 0.1.0-alpha2
- Clojure.$(AssemblyName)
- ClojureCLR contributors
- The deps.edn-powered CLI tool for ClojureCLR.
- ClojureCLR contributors, 2024
- https://github.com/clojure/clr.core.cli
- https://github.com/clojure/clr.core.cli
- EPL-1.0
- Clojure;ClojureCLR
-
+
+ Exe
+ net6.0;net8.0
+ 12
+ enable
+ enable
+ true
+ cljr
+ true
+ ./nupkg
+ 0.1.0-alpha2
+ Clojure.$(AssemblyName)
+ ClojureCLR contributors
+ The deps.edn-powered CLI tool for ClojureCLR.
+ ClojureCLR contributors, 2024
+ https://github.com/clojure/clr.core.cli
+ https://github.com/clojure/clr.core.cli
+ EPL-1.0
+ Clojure;ClojureCLR
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
diff --git a/src/dotnet/Cljr/CommandLineParser.cs b/src/dotnet/Cljr/CommandLineParser.cs
index 14b4f67..aa9bba0 100644
--- a/src/dotnet/Cljr/CommandLineParser.cs
+++ b/src/dotnet/Cljr/CommandLineParser.cs
@@ -1,6 +1,5 @@
namespace Cljr;
-
public static class CommandLineParser
{
static readonly List DeprecatedPrefixes = new() { "-R", "-C", "-O" };
@@ -19,7 +18,7 @@ public static ParseItems Parse(string[] args)
var arg = args[i++];
// PowerShell workaround
- if (Program.IsWindows)
+ if (Platform.IsWindows)
{
switch (arg)
{
@@ -36,7 +35,8 @@ public static ParseItems Parse(string[] args)
}
if (StartsWithDeprecatedPrefix(arg))
- return items.SetError($"{arg[..2]} is no longer supported, use -A with repl, -M for main, -X for exec, -T for tool");
+ return items.SetError(
+ $"{arg[..2]} is no longer supported, use -A with repl, -M for main, -X for exec, -T for tool");
if (arg == "-Sresolve-tags")
return items.SetError("Option changed, use: clj -X:deps git-resolve-tags");
@@ -106,6 +106,7 @@ public static ParseItems Parse(string[] args)
default:
return items.SetError($"Unknown option: {arg}");
}
+
continue;
}
@@ -165,5 +166,4 @@ public static ParseItems Parse(string[] args)
return items;
}
-
-}
+}
\ No newline at end of file
diff --git a/src/dotnet/Cljr/ParseItems.cs b/src/dotnet/Cljr/ParseItems.cs
index 93de929..87f80f3 100644
--- a/src/dotnet/Cljr/ParseItems.cs
+++ b/src/dotnet/Cljr/ParseItems.cs
@@ -1,6 +1,14 @@
namespace Cljr;
-public enum EMode { Version, Help, Repl, Tool, Exec, Main }
+public enum EMode
+{
+ Version,
+ Help,
+ Repl,
+ Tool,
+ Exec,
+ Main
+}
public class ParseItems
{
@@ -35,19 +43,13 @@ public void SetCommandAliases(EMode mode, string? alias)
CommandAliases[mode] = alias;
}
- public string GetCommandAlias(EMode mode)
- {
- if (CommandAliases.TryGetValue(mode, out var alias))
- return alias;
- else
- return string.Empty;
- }
-
- public bool TryGetCommandAlias(EMode mode, out string alias)
- {
- return CommandAliases.TryGetValue(mode, value: out alias);
- }
+ public string GetCommandAlias(EMode mode) =>
+ CommandAliases.TryGetValue(mode, out var alias)
+ ? alias
+ : string.Empty;
+ public bool TryGetCommandAlias(EMode mode, out string? alias) =>
+ CommandAliases.TryGetValue(mode, value: out alias);
public void AddFlag(string flag)
{
diff --git a/src/dotnet/Cljr/Platform.cs b/src/dotnet/Cljr/Platform.cs
new file mode 100644
index 0000000..5c189eb
--- /dev/null
+++ b/src/dotnet/Cljr/Platform.cs
@@ -0,0 +1,11 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace Cljr;
+
+public static class Platform
+{
+ public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ public static string HomeDir => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ public static readonly string Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown";
+}
\ No newline at end of file
diff --git a/src/dotnet/Cljr/Program.cs b/src/dotnet/Cljr/Program.cs
index acae639..15db078 100644
--- a/src/dotnet/Cljr/Program.cs
+++ b/src/dotnet/Cljr/Program.cs
@@ -1,526 +1,512 @@
using System.Diagnostics;
-using System.Runtime.InteropServices;
using System.Security.Cryptography;
+using Cljr;
-namespace Cljr;
+var cliArgs = CommandLineParser.Parse(args);
-public class Program
+if (cliArgs.IsError)
{
- public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- static string HomeDir => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
-
- public static void PrintHelp()
- {
- Console.WriteLine($"Version: {Version}");
- Console.WriteLine();
- Console.WriteLine(@"You use the Clojure tools('clj' or 'clojure') to run Clojure programs
-on the JVM, e.g.to start a REPL or invoke a specific function with data.
-The Clojure tools will configure the JVM process by defining a classpath
-(of desired libraries), an execution environment(JVM options) and
-specifying a main class and args.
-
-Using a deps.edn file (or files), you tell Clojure where your source code
-resides and what libraries you need.Clojure will then calculate the full
-set of required libraries and a classpath, caching expensive parts of this
-process for better performance.
-
-Note: For projects intended for both Clojure and ClojureCLR, it might be
-necessary to supply a different deps.edn file for each platform. The
-ClojureCLR version of the file should be named deps-clr.edn. When running
-ClojureCLR, the deps-clr.edn in preference to deps.edn should both exist.
-
-The internal steps of the Clojure tools, as well as the Clojure functions
-you intend to run, are parameterized by data structures, often maps.Shell
-command lines are not optimized for passing nested data, so instead you
-will put the data structures in your deps.edn file and refer to them on the
-command line via 'aliases' - keywords that name data structures.
-
-'clj' and 'clojure' differ in that 'clj' has extra support for use as a REPL
-in a terminal, and should be preferred unless you don't want that support,
-then use 'clojure'.
-
-Usage:
- Start a REPL clj [clj-opt*] [-Aaliases]
- Exec fn(s) clojure [clj-opt*] -X[aliases][a / fn *][kpath v]*
- Run main clojure[clj - opt *] -M[aliases][init - opt *][main - opt][arg *]
- Run tool clojure [clj-opt*] -T[name | aliases] a/fn[kpath v] kv-map?
- Prepare clojure[clj - opt *] -P[other exec opts]
-
-exec-opts:
- -Aaliases Use concatenated aliases to modify classpath
- -X[aliases] Use concatenated aliases to modify classpath or supply exec fn/args
- -M[aliases] Use concatenated aliases to modify classpath or supply main opts
- -P Prepare deps - download libs, cache classpath, but don't exec
-
-clj-opts:
- -Jopt Pass opt through in java_opts, ex: -J-Xmx512m
- -Sdeps EDN Deps data to use as the last deps file to be merged
- -Spath Compute classpath and echo to stdout only
- -Stree Print dependency tree
- -Scp CP Do NOT compute or cache classpath, use this one instead
- -Srepro Ignore the ~/.clojure/deps.edn config file
- -Sforce Force recomputation of the classpath(don't use the cache)
- -Sverbose Print important path info to console
- -Sdescribe Print environment and command parsing info as data
- -Sthreads Set specific number of download threads
- -Strace Write a trace.edn file that traces deps expansion
- -- Stop parsing dep options and pass remaining arguments to clojure.main
- --version Print the version to stdout and exit
- -version Print the version to stdout and exit
-
-The following non-standard options are available only in deps.clj:
-
- -Sdeps-file Use this file instead of deps.edn
- -Scommand A custom command that will be invoked. Substitutions: { { classpath} }, {{main-opts
-}}.
-
-init - opt:
- -i, --init path Load a file or resource
- -e, --eval string Eval exprs in string; print non-nil values
- --report target Report uncaught exception to ""file"" (default), ""stderr"", or ""none""
-
-main-opt:
- -m, --main ns - name Call the -main function from namespace w/args
- -r, --repl Run a repl
- path Run a script from a file or resource
- - Run a script from standard input
- -h, -?, --help Print this help message and exit
-
-Programs provided by :deps alias:
- -X:deps mvn-pom Generate (or update) pom.xml with deps and paths
- -X:deps list List full transitive deps set and licenses
- -X:deps tree Print deps tree
- -X:deps find-versions Find available versions of a library
- -X:deps prep Prepare all unprepped libs in the dep tree
- -X:deps mvn-install Install a maven jar to the local repository cache
- -X:deps git-resolve-tags Resolve git coord tags to shas and update deps.edn
-
-For more info, see:
- https://clojure.org/guides/deps_and_cli
- https://clojure.org/reference/repl_and_main");
- }
-
- static void PrintVersion()
- {
- Console.WriteLine($"ClojureCLR CLI Version: {Version}");
- }
-
-
- static void Warn(string message) => Console.Error.WriteLine(message);
-
- //static void EndExecution(int exitCode, string message)
- //{
- // Warn(message);
- // EndExecution(exitCode);
- //}
-
- //static void EndExecution(int exitCode) => Environment.Exit(exitCode);
+ Warn(cliArgs.ErrorMessage!);
+ return 1;
+}
- static readonly string Version = typeof(Program).Assembly.GetName().Version!.ToString();
+if (cliArgs.Mode is EMode.Help)
+{
+ PrintHelp();
+ return 0;
+}
+if (cliArgs.Mode is EMode.Version)
+{
+ PrintVersion();
+ return 0;
+}
- static bool IsNewerFile(string filename1, string filename2)
- {
- if (!File.Exists(filename1)) return false;
- if (!File.Exists(filename2)) return true;
- var mod1 = new FileInfo(filename1).LastWriteTimeUtc;
- var mod2 = new FileInfo(filename2).LastWriteTimeUtc;
- return mod1 > mod2;
- }
+if (cliArgs.HasFlag("pom"))
+ Warn("We are CLR! We don't do -Spom");
- static string GetStringHash(string s)
- {
- var hash = MD5.Create().ComputeHash(System.Text.UTF8Encoding.UTF8.GetBytes(s));
- return BitConverter.ToString(hash);
- }
+if (cliArgs.JvmOpts.Count > 0)
+ Warn("We are CLR! -Jjvm_opts aren't going to do you much good.");
+var installDir = AppContext.BaseDirectory;
+//var toolsCp = Path.Combine(InstallDir, $"clojure-tools-{Version}.jar"); // TODO -- what do we do instead?
+// Determine user config directory; if it does not exist, create it
+var configDir = Environment.GetEnvironmentVariable("CLJ_CONFIG") ?? Path.Join(Platform.HomeDir, ".clojure");
+if (!Directory.Exists(configDir))
+ Directory.CreateDirectory(configDir);
- static int Main(string[] args)
- {
- var cliArgs = CommandLineParser.Parse(args);
+const string defaultConfigFile = "deps.edn";
+string[] depsFiles = ["deps-clr.edn", defaultConfigFile];
- if (cliArgs.IsError)
- {
- Warn(cliArgs.ErrorMessage!);
- return 1;
- }
+// Copy in example deps.edn if no deps.edn in the configDir
+if (depsFiles.Select(f => Path.Join(configDir, f)).FirstOrDefault(File.Exists) is not { } configUser)
+{
+ configUser = Path.Join(configDir, defaultConfigFile);
+ File.Copy(Path.Join(installDir, "example-deps.edn"), configUser);
+}
- if (cliArgs.Mode == EMode.Help)
- {
- PrintHelp();
- return 0;
- }
+var configToolsDir = Path.Join(configDir, "tools");
+// Make sure the configDir tools directory exists.
+{
+ if (!Directory.Exists(configToolsDir))
+ Directory.CreateDirectory(configToolsDir);
+}
- if (cliArgs.Mode == EMode.Version)
- {
- PrintVersion();
- return 0;
- }
+// Make sure the tools.edn file is up-to-date.
+{
+ var installToolsEdn = Path.Join(installDir, "tools.edn");
+ var configToolsEdn = Path.Join(configToolsDir, "tools.edn");
+ if (IsNewerFile(installToolsEdn, configToolsEdn))
+ File.Copy(installToolsEdn, configToolsEdn, true);
+}
- if (cliArgs.HasFlag("pom"))
- Warn("We are CLR! We don't do -Spom");
+// Determine the user cache directory
+var userCacheDir = Environment.GetEnvironmentVariable("CLJ_CACHE") ?? Path.Join(configDir, ".cpcache");
+
+// Chain deps.edn in config paths. repro=skip config dir
+List configPaths =
+[
+ Path.Join(installDir, defaultConfigFile),
+ defaultConfigFile
+];
+
+// I think in this case it does not matter whether we have deps.edn or deps-clr.edn.
+// This is used only to determine wither the user config file is being used or not.
+// Which actual project file is used is not really relevant.
+if (!cliArgs.HasFlag("repro"))
+ configPaths.Insert(1, configUser);
+
+// Determine whether to use user or project cache
+var configProject = depsFiles.FirstOrDefault(File.Exists);
+var cacheDir = configProject is not null ? ".cpcache" : userCacheDir;
+configProject ??= defaultConfigFile;
+
+// Construct location of cached classpath file
+const string cacheVersion = "4";
+
+var cacheKeyHash = GetStringHash([
+ cacheVersion,
+ cliArgs.GetCommandAlias(EMode.Repl),
+ cliArgs.GetCommandAlias(EMode.Exec),
+ cliArgs.GetCommandAlias(EMode.Main),
+ cliArgs.GetCommandAlias(EMode.Tool),
+ cliArgs.Deps ?? string.Empty,
+ cliArgs.ToolName ?? string.Empty,
+ ..configPaths
+]);
+
+var cpFile = Path.Join(cacheDir, $"{cacheKeyHash}.cp");
+var jvmFile = Path.Join(cacheDir, $"{cacheKeyHash}.jvm");
+var mainFile = Path.Join(cacheDir, $"{cacheKeyHash}.main");
+var basisFile = Path.Join(cacheDir, $"{cacheKeyHash}.basis");
+var manifestFile = Path.Join(cacheDir, $"{cacheKeyHash}.manifest");
+
+if (cliArgs.HasFlag("verbose"))
+ Print($"""
+ version = {Platform.Version}
+ install_dir = {installDir}
+ config_dir = {configDir}
+ config_paths = {string.Join(' ', configPaths)}
+ cache_dir = {cacheDir}
+ cp_file = {cpFile}
+ """);
+
+// check for stale classpath
+var stale = false;
+
+if (cliArgs.HasFlag("force") || cliArgs.HasFlag("trace") || cliArgs.HasFlag("tree") ||
+ cliArgs.HasFlag("prep") || !File.Exists(cpFile))
+ stale = true;
+else if (cliArgs.ToolName is not null && IsNewerFile(Path.Join(configToolsDir, $"{cliArgs.ToolName}.edn"), cpFile))
+ stale = true;
+else if (configPaths.ToList().Exists(p => IsNewerFile(p, cpFile)))
+ stale = true;
+
+// test for manifest?
+//if (Test - Path $ManifestFile) {
+// $Manifests = @(Get - Content $ManifestFile)
+// if ($Manifests | Where - Object { !(Test - Path $_) -or(Test - NewerFile $_ $CpFile) }) {
+// $Stale = $TRUE
+// }
+
+// Make tools args if needed
+List toolsArgs = [];
+
+if (stale || cliArgs.HasFlag("pom"))
+{
+ if (cliArgs.Deps is not null)
+ toolsArgs.AddRange(["--config-data", cliArgs.Deps]);
- if (cliArgs.JvmOpts.Count > 0)
- Warn("We are CLR! -Jjvm_opts aren't going to do you much good.");
+ if (cliArgs.TryGetCommandAlias(EMode.Main, out var alias))
+ toolsArgs.Add($"-M{alias}");
- var installDir = AppContext.BaseDirectory;
- //var toolsCp = Path.Combine(InstallDir, $"clojure-tools-{Version}.jar"); // TODO -- what do we do instead?
+ if (cliArgs.TryGetCommandAlias(EMode.Repl, out alias))
+ toolsArgs.Add($"-A{alias}");
- // Determine user config directory; if it does not exist, create it
- var configDir = Environment.GetEnvironmentVariable("CLJ_CONFIG")
- ?? Path.Join(HomeDir, ".clojure");
- if (!Directory.Exists(configDir))
- Directory.CreateDirectory(configDir);
+ if (cliArgs.TryGetCommandAlias(EMode.Exec, out alias))
+ toolsArgs.Add($"-X{alias}");
- // Copy in example deps.edn if no deps.edn in the configDir
- if (!File.Exists(Path.Join(configDir,"deps-clr.edn")) && !File.Exists(Path.Join(configDir, "deps.edn")))
- File.Copy(
- Path.Join(installDir, "example-deps.edn"),
- Path.Join(configDir, "deps.edn"));
+ if (cliArgs.Mode == EMode.Tool)
+ toolsArgs.Add("--tool-mode");
- // Make sure the configDir tools directory exists.
- {
- var configToolsDir = Path.Join(configDir, "tools");
+ if (cliArgs.ToolName is not null)
+ toolsArgs.AddRange(["--tool-name", cliArgs.ToolName]);
- if (!Directory.Exists(configToolsDir))
- Directory.CreateDirectory(configToolsDir);
- }
+ if (cliArgs.TryGetCommandAlias(EMode.Tool, out alias))
+ toolsArgs.Add($"-T{alias}");
- // Make sure the tools.edn file is up-to-date.
- {
- var installToolsEdn = Path.Join(installDir, "tools.edn");
- var configToolsEdn = Path.Join(configDir, "tools", "tools.edn");
- if (IsNewerFile(installToolsEdn, configToolsEdn))
- File.Copy(installToolsEdn, configToolsEdn, true);
- }
+ if (cliArgs.ForceClasspath is not null)
+ toolsArgs.Add("--skip-cp");
- // Determine the user cache directory
- string userCacheDir = Environment.GetEnvironmentVariable("CLJ_CACHE") ?? Path.Join(configDir, ".cpcache");
+ if (cliArgs.Threads != 0)
+ toolsArgs.AddRange(["--threads", cliArgs.Threads.ToString()]);
- // Chain deps.edn in config paths. repro=skip config dir
- var configProject = "deps.edn";
- string configUser = string.Empty;
- string[] configPaths;
+ if (cliArgs.HasFlag("trace"))
+ toolsArgs.Add("--trace");
- // I think in this case it does not matter whether whether we have deps.edn or deps-clr.edn.
- // This is used only to determine wither the user config file is being used or not.
- // Which actual project file is used is not really relevant.
- if (cliArgs.HasFlag("repro"))
- {
- configPaths = new string[] { Path.Join(installDir, "deps.edn"), "deps.edn" };
- }
- else
- {
- configUser = Path.Join(configDir, "deps.edn");
- configPaths = new string[] { Path.Join(installDir, "deps.edn"), configUser, "deps.edn" };
- }
+ if (cliArgs.HasFlag("tree"))
+ toolsArgs.Add("--tree");
+}
- // Determine whether to use user or project cache
- var cacheDir = File.Exists("deps-clr.edn") || File.Exists("deps.edn") ? ".cpcache" : userCacheDir;
-
- // Construct location of cached classpath file
- var cacheVersion = "4";
- string replAliases = cliArgs.GetCommandAlias(EMode.Repl);
- string execAliases = cliArgs.GetCommandAlias(EMode.Exec);
- string mainAliases = cliArgs.GetCommandAlias(EMode.Main);
- string toolAliases = cliArgs.GetCommandAlias(EMode.Tool);
- string depsData = cliArgs.Deps ?? string.Empty;
- string toolName = cliArgs.ToolName ?? string.Empty;
- string configPathString = String.Join('|', configPaths);
-
- var cacheKey = $"{cacheVersion}|{replAliases}|{execAliases}|{mainAliases}|{depsData}|{toolName}|{toolAliases}|{configPathString}";
- var cacheKeyHash = GetStringHash(cacheKey).Replace("-", "");
-
- var cpFile = Path.Join(cacheDir, $"{cacheKeyHash}.cp");
- var jvmFile = Path.Join(cacheDir, $"{cacheKeyHash}.jvm");
- var mainFile = Path.Join(cacheDir, $"{cacheKeyHash}.main");
- var basisFile = Path.Join(cacheDir, $"{cacheKeyHash}.basis");
- var manifestFile = Path.Join(cacheDir, $"{cacheKeyHash}.manifest");
-
- if (cliArgs.HasFlag("verbose"))
- {
- Console.WriteLine($"version = {Version}");
- Console.WriteLine($"install_dir = {installDir}");
- Console.WriteLine($"config_dir = {configDir}");
- Console.WriteLine($"config_paths = {String.Join(' ', configPaths)}");
- Console.WriteLine($"cache_dir = {cacheDir}");
- Console.WriteLine($"cp_file = {cpFile}");
- Console.WriteLine();
- }
- // check for stale classpath
- var stale = false;
-
- if (cliArgs.HasFlag("force") || cliArgs.HasFlag("trace") || cliArgs.HasFlag("tree") || cliArgs.HasFlag("prep") || !File.Exists(cpFile))
- stale = true;
- else if (cliArgs.ToolName is not null && IsNewerFile(Path.Join(configDir, "tools", $"{cliArgs.ToolName}.edn"), cpFile))
- stale = true;
- else if (configPaths.ToList().Exists(p => IsNewerFile(p, cpFile)))
- stale = true;
- // test for manifest?
- //if (Test - Path $ManifestFile) {
- // $Manifests = @(Get - Content $ManifestFile)
- // if ($Manifests | Where - Object { !(Test - Path $_) -or(Test - NewerFile $_ $CpFile) }) {
- // $Stale = $TRUE
- // }
-
- // Make tools args if needed
- List toolsArgs = new();
-
- if (stale || cliArgs.HasFlag("pom"))
- {
- if (cliArgs.Deps is not null)
+// If stale, run make-classpath to refresh cached classpath
+if (stale && !cliArgs.HasFlag("describe"))
+{
+ if (cliArgs.HasFlag("verbose"))
+ Print("Refreshing classpath");
+
+ // TODO: MAKE PROCESS CALL CORRESPONDING TO:
+ // & $JavaCmd - XX:-OmitStackTraceInFastThrow @CljJvmOpts -classpath $ToolsCp clojure.main -m clojure.tools.deps.script.make-classpath2
+ // --config-user $ConfigUser
+ // --config-project $ConfigProject
+ // --basis-file $BasisFile
+ // --cp-file $CpFile
+ // --jvm-file $JvmFile
+ // --main-file $MainFile
+ // --manifest-file $ManifestFile @ToolsArgs
+ // if ($LastExitCode - ne 0) {
+ // return
+
+ try
+ {
+ using var process = CreateClojureProcess(
+ installDir,
+ args:
+ [
+ "-m", "clojure.tools.deps.script.make-classpath2",
+ "--install-dir", installDir.Replace(@"\", @"\\"),
+ "--config-user", configUser,
+ "--config-project", configProject,
+ "--basis-file", basisFile,
+ "--cp-file", cpFile,
+ "--jvm-file", jvmFile,
+ "--main-file", mainFile,
+ "--manifest-file", manifestFile,
+ // incredible hack to get around dealing with what the powershell parser does to args with a : in them
+ ..toolsArgs.Select(arg => $"\"{arg}\""),
+ ],
+ env: new()
{
- toolsArgs.Add("--config-data");
- toolsArgs.Add((string)cliArgs.Deps);
+ ["CLOJURE_LOAD_PATH"] = installDir,
}
- if (cliArgs.TryGetCommandAlias(EMode.Main, out var alias))
- toolsArgs.Add($"-M{alias}");
-
- if (cliArgs.TryGetCommandAlias(EMode.Repl, out alias))
- toolsArgs.Add($"-A{alias}");
+ );
+
+ //Print($"Classpath: toolsArg = {string.Join(' ', toolsArgs)}");
+ //Print($"Classpath: argList = {string.Join(' ', argList)}");
+ //Print($"Classpath: installdir = {installDir}");
+ process.Start();
+ process.WaitForExit();
+ if (process.ExitCode is not 0)
+ return process.ExitCode;
+
+ if (!File.Exists(cpFile))
+ throw new InvalidOperationException();
+ }
+ catch (Exception ex)
+ {
+ Warn($"Error creating classpath: {ex.Message}");
+ return 1;
+ }
+}
- if (cliArgs.TryGetCommandAlias(EMode.Exec, out alias))
- toolsArgs.Add($"-X{alias}");
+var classpath =
+ cliArgs.HasFlag("describe")
+ ? string.Empty
+ : cliArgs.ForceClasspath ?? File.ReadAllText(cpFile);
- if (cliArgs.Mode == EMode.Tool)
- toolsArgs.Add("--tool-mode");
+if (cliArgs.HasFlag("prep"))
+{
+ /* already done */
+}
+else if (cliArgs.HasFlag("pom"))
+{
+ // TODO -- are we doing this?
+ // & $JavaCmd -XX:-OmitStackTraceInFastThrow @CljJvmOpts -classpath $ToolsCp clojure.main -m clojure.tools.deps.script.generate-manifest2 --config-user $ConfigUser --config-project $ConfigProject --gen=pom @ToolsArgs
+}
+else if (cliArgs.HasFlag("path"))
+{
+ Print(classpath);
+}
+else if (cliArgs.HasFlag("describe"))
+{
+ var pathVector = string.Join(' ', configPaths.Select(p => p.Replace(@"\", @"\\")));
+ Print(
+ $$"""
+ {:version {{Platform.Version}}
+ :config-files [{{pathVector}}]
+ :config-user {{configUser.Replace(@"\", @"\\")}}
+ :config-project {{configProject.Replace(@"\", @"\\")}}
+ :install-dir {{installDir.Replace(@"\", @"\\")}}
+ :config-dir {{configDir.Replace(@"\", @"\\")}}
+ :cache-dir {{cacheDir.Replace(@"\", @"\\")}}
+ :force {{cliArgs.HasFlag("force")}}
+ :repro {{cliArgs.HasFlag("repro")}}
+ :main - aliases {{cliArgs.GetCommandAlias(EMode.Main)}}
+ :repl - aliases {{cliArgs.GetCommandAlias(EMode.Repl)}}
+ :exec - aliases {{cliArgs.GetCommandAlias(EMode.Exec)}}
+ }
+ """
+ );
+}
+else if (cliArgs.HasFlag("tree"))
+{
+ /* already done */
+}
+else if (cliArgs.HasFlag("trace"))
+{
+ Print("Wrote trace.edn");
+}
+else
+{
+ //if (Test - Path $JvmFile) {
+ // $JvmCacheOpts = @(Get - Content $JvmFile)
- if (cliArgs.ToolName is not null)
+ if (cliArgs.Mode is EMode.Exec or EMode.Tool)
+ {
+ // & $JavaCmd -XX:-OmitStackTraceInFastThrow @JavaOpts @JvmCacheOpts @JvmOpts "-Dclojure.basis=$BasisFile" -classpath "$CP;$InstallDir/exec.jar" clojure.main -m clojure.run.exec @ClojureArgs
+
+ Print("Starting exec/tool");
+
+ using var process = CreateClojureProcess(
+ installDir,
+ args:
+ [
+ "-m", "clojure.run.exec",
+ ..cliArgs.CommandArgs,
+ ],
+ env: new()
{
- toolsArgs.Add("--tool-name");
- toolsArgs.Add(cliArgs.ToolName);
+ ["CLOJURE_LOAD_PATH"] =
+ $"{classpath}{Path.PathSeparator}{installDir}", // TODO -- what is this? need to get the equivalent of exec.jar on the load path
+ ["clojure.basis"] = basisFile,
+ ["clojure.cli.install-dir"] = installDir,
}
+ );
- if (cliArgs.TryGetCommandAlias(EMode.Tool, out alias))
- toolsArgs.Add($"-T{alias}");
-
- if (cliArgs.ForceClasspath is not null)
- toolsArgs.Add("--skip-cp");
-
- if (cliArgs.Threads != 0)
+ process.Start();
+ process.WaitForExit();
+ }
+ else
+ {
+ // if (Test - Path $MainFile) {
+ // # TODO this seems dangerous
+ // $MainCacheOpts = @(Get - Content $MainFile) -replace '"', '\"'
+ // }
+ var mainCacheOpts = File.Exists(mainFile) ? File.ReadAllLines(mainFile).ToList() : [];
+
+ if (cliArgs.CommandArgs.Count > 0 && cliArgs.HasFlag("repl"))
+ Warn("WARNING: Implicit use of clojure.main with options is deprecated, use -M");
+
+ // & $JavaCmd - XX:-OmitStackTraceInFastThrow @JavaOpts @JvmCacheOpts @JvmOpts -Dclojure.basis=$BasisFile -classpath $CP clojure.main @MainCacheOpts @ClojureArgs
+
+ //using Process process = new();
+ //process.StartInfo.UseShellExecute = false;
+ //process.StartInfo.FileName = "powershell.exe";
+ //process.StartInfo.CreateNoWindow = false; // TODO: When done debugging, set to true
+ //process.StartInfo.WorkingDirectory = Environment.CurrentDirectory;
+ //var env = process.StartInfo.EnvironmentVariables;
+ //env["CLOJURE_LOAD_PATH"] = classpath; // TODO -- what is this?
+ //env["clojure.basis"] = basisFile; // will this do -Dclojure.basis=$BasisFile ?
+ //var argList = process.StartInfo.ArgumentList;
+ //argList.Add(Path.Join(installDir, Path.Join("tools", "run-clojure-main.ps1")));
+ //argList.Add("-e");
+ //argList.Add("\'(println 12)(load \\\"hello\\\")(println (hello/run))\'");
+ //process.Start();
+ //process.WaitForExit();
+
+ Print("Starting main");
+
+ using var process = CreateClojureProcess(
+ installDir,
+ args:
+ [
+ ..mainCacheOpts.Select(arg => arg.Replace(@"""", @"\""")),
+ ..cliArgs.CommandArgs,
+ ],
+ env: new()
{
- toolsArgs.Add("--threads");
- toolsArgs.Add(cliArgs.Threads.ToString());
+ ["CLOJURE_LOAD_PATH"] = classpath, // TODO -- what is this?
+ ["clojure.basis"] = basisFile, // will this do -Dclojure.basis=$BasisFile ?
}
+ );
- if (cliArgs.HasFlag("trace"))
- toolsArgs.Add("--trace");
-
- if (cliArgs.HasFlag("tree"))
- toolsArgs.Add("--tree");
- }
-
+ process.StartInfo.WorkingDirectory = Environment.CurrentDirectory;
+ process.Start();
+ process.WaitForExit();
+ }
+}
- // If stale, run make-classpath to refresh cached classpath
- if (stale && !cliArgs.HasFlag("describe"))
- {
- if (cliArgs.HasFlag("verbose"))
- Console.WriteLine("Refreshing classpath");
-
- // TODO: MAKE PROCESS CALL CORRESPONDING TO:
- // & $JavaCmd - XX:-OmitStackTraceInFastThrow @CljJvmOpts -classpath $ToolsCp clojure.main -m clojure.tools.deps.script.make-classpath2
- // --config-user $ConfigUser
- // --config-project $ConfigProject
- // --basis-file $BasisFile
- // --cp-file $CpFile
- // --jvm-file $JvmFile
- // --main-file $MainFile
- // --manifest-file $ManifestFile @ToolsArgs
- // if ($LastExitCode - ne 0) {
- // return
-
- try
- {
- using Process process = new();
- SetInitialProcessParameters(process, installDir);
-
- var env = process.StartInfo.EnvironmentVariables;
- env["CLOJURE_LOAD_PATH"] = installDir;
-
- var argList = process.StartInfo.ArgumentList;
- argList.Add("-m");
- argList.Add("clojure.tools.deps.script.make-classpath2");
- argList.Add("--install-dir");
- argList.Add(installDir.Replace("\\", "\\\\"));
- argList.Add("--config-user");
- argList.Add(configUser);
- argList.Add("--config-project");
- argList.Add(configProject);
- argList.Add("--basis-file");
- argList.Add(basisFile);
- argList.Add("--cp-file");
- argList.Add(cpFile);
- argList.Add("--jvm-file");
- argList.Add(jvmFile);
- argList.Add("--main-file");
- argList.Add(mainFile);
- argList.Add("--manifest-file");
- argList.Add(manifestFile);
-
- toolsArgs.ForEach(arg => argList.Add("\"" + arg + "\"")); // incredible hack to get around dealing with what the powershell parser does to args with a : in them
-
- //Console.WriteLine($"Classpath: toolsArg = {string.Join(' ', toolsArgs)}");
- //Console.WriteLine($"Classpath: argList = {string.Join(' ', argList)}");
- //Console.WriteLine($"Classpath: installdir = {installDir}");
-
- process.Start();
- process.WaitForExit();
- if (process.ExitCode != 0)
- return process.ExitCode;
- }
- catch (Exception ex)
- {
- Warn($"Error creating classpath: {ex.Message}");
- return 1;
- }
- }
+return 0;
+
+static void PrintHelp() => Print(
+ $$$"""
+ Version: {{{Platform.Version}}}
+
+ You use the Clojure tools('clj' or 'clojure') to run Clojure programs
+ on the JVM, e.g.to start a REPL or invoke a specific function with data.
+ The Clojure tools will configure the JVM process by defining a classpath
+ (of desired libraries), an execution environment(JVM options) and
+ specifying a main class and args.
+
+ Using a deps.edn file (or files), you tell Clojure where your source code
+ resides and what libraries you need.Clojure will then calculate the full
+ set of required libraries and a classpath, caching expensive parts of this
+ process for better performance.
+
+ Note: For projects intended for both Clojure and ClojureCLR, it might be
+ necessary to supply a different deps.edn file for each platform. The
+ ClojureCLR version of the file should be named deps-clr.edn. When running
+ ClojureCLR, the deps-clr.edn in preference to deps.edn should both exist.
+
+ The internal steps of the Clojure tools, as well as the Clojure functions
+ you intend to run, are parameterized by data structures, often maps.Shell
+ command lines are not optimized for passing nested data, so instead you
+ will put the data structures in your deps.edn file and refer to them on the
+ command line via 'aliases' - keywords that name data structures.
+
+ 'clj' and 'clojure' differ in that 'clj' has extra support for use as a REPL
+ in a terminal, and should be preferred unless you don't want that support,
+ then use 'clojure'.
+
+ Usage:
+ Start a REPL clj clj-opt* [-Aaliases]
+ Exec fn(s) clojure [clj-opt*] -X[aliases][a / fn *][kpath v]*
+ Run main clojure[clj - opt *] -M[aliases][init - opt *][main - opt][arg *]
+ Run tool clojure [clj-opt*] -T[name | aliases] a/fn[kpath v] kv-map?
+ Prepare clojure[clj - opt *] -P[other exec opts]
+
+ exec-opts:
+ -Aaliases Use concatenated aliases to modify classpath
+ -X[aliases] Use concatenated aliases to modify classpath or supply exec fn/args
+ -M[aliases] Use concatenated aliases to modify classpath or supply main opts
+ -P Prepare deps - download libs, cache classpath, but don't exec
+
+ clj-opts:
+ -Jopt Pass opt through in java_opts, ex: -J-Xmx512m
+ -Sdeps EDN Deps data to use as the last deps file to be merged
+ -Spath Compute classpath and echo to stdout only
+ -Stree Print dependency tree
+ -Scp CP Do NOT compute or cache classpath, use this one instead
+ -Srepro Ignore the ~/.clojure/deps.edn config file
+ -Sforce Force recomputation of the classpath(don't use the cache)
+ -Sverbose Print important path info to console
+ -Sdescribe Print environment and command parsing info as data
+ -Sthreads Set specific number of download threads
+ -Strace Write a trace.edn file that traces deps expansion
+ -- Stop parsing dep options and pass remaining arguments to clojure.main
+ --version Print the version to stdout and exit
+ -version Print the version to stdout and exit
+
+ The following non-standard options are available only in deps.clj:
+
+ -Sdeps-file Use this file instead of deps.edn
+ -Scommand A custom command that will be invoked. Substitutions: { { classpath} }, {{main-opts
+ }}.
+
+ init - opt:
+ -i, --init path Load a file or resource
+ -e, --eval string Eval exprs in string; print non-nil values
+ --report target Report uncaught exception to ""file"" (default), ""stderr"", or ""none""
+
+ main-opt:
+ -m, --main ns - name Call the -main function from namespace w/args
+ -r, --repl Run a repl
+ path Run a script from a file or resource
+ - Run a script from standard input
+ -h, -?, --help Print this help message and exit
+
+ Programs provided by :deps alias:
+ -X:deps mvn-pom Generate (or update) pom.xml with deps and paths
+ -X:deps list List full transitive deps set and licenses
+ -X:deps tree Print deps tree
+ -X:deps find-versions Find available versions of a library
+ -X:deps prep Prepare all unprepped libs in the dep tree
+ -X:deps mvn-install Install a maven jar to the local repository cache
+ -X:deps git-resolve-tags Resolve git coord tags to shas and update deps.edn
+
+ For more info, see:
+ https://clojure.org/guides/deps_and_cli
+ https://clojure.org/reference/repl_and_main
+ """
+);
+
+static void PrintVersion() => Print($"ClojureCLR CLI Version: {Platform.Version}");
+static void Warn(string message) => Console.Error.WriteLine(message);
+static void Print(string message) => Console.WriteLine(message);
+
+//static void EndExecution(int exitCode, string message)
+//{
+// Warn(message);
+// EndExecution(exitCode);
+//}
+
+//static void EndExecution(int exitCode) => Environment.Exit(exitCode);
+
+static bool IsNewerFile(string filename1, string filename2)
+{
+ if (!File.Exists(filename1)) return false;
+ if (!File.Exists(filename2)) return true;
+ var mod1 = new FileInfo(filename1).LastWriteTimeUtc;
+ var mod2 = new FileInfo(filename2).LastWriteTimeUtc;
+ return mod1 > mod2;
+}
- var classpath =
- cliArgs.HasFlag("describe") ? ""
- : cliArgs.ForceClasspath
- ?? File.ReadAllText(cpFile);
+static string GetStringHash(params string[] s)
+{
+ var cacheKey = string.Join('|', s);
+ var hash = MD5.HashData(System.Text.Encoding.UTF8.GetBytes(cacheKey));
+ return BitConverter.ToString(hash).Replace("-", "");
+}
- if (cliArgs.HasFlag("prep"))
- { /* already done */ }
- else if (cliArgs.HasFlag("pom"))
- {
- // TODO -- are we doing this?
- // & $JavaCmd -XX:-OmitStackTraceInFastThrow @CljJvmOpts -classpath $ToolsCp clojure.main -m clojure.tools.deps.script.generate-manifest2 --config-user $ConfigUser --config-project $ConfigProject --gen=pom @ToolsArgs
- }
- else if (cliArgs.HasFlag("path"))
- {
- Console.WriteLine(classpath);
- }
- else if (cliArgs.HasFlag("describe"))
- {
- var pathVector = String.Join(' ', configPaths.Select(p => p.Replace("\\", "\\\\")).ToArray());
- Console.WriteLine($"{{:version {Version}");
- Console.WriteLine($" :config-files [{pathVector}]");
- Console.WriteLine($" :config-user {configUser.Replace("\\", "\\\\")}");
- Console.WriteLine($" :config-project {configProject.Replace("\\", "\\\\")}");
- Console.WriteLine($" :install-dir {installDir.Replace("\\", "\\\\")}");
- Console.WriteLine($" :config-dir {configDir.Replace("\\", "\\\\")}");
- Console.WriteLine($" :cache-dir {cacheDir.Replace("\\", "\\\\")}");
- Console.WriteLine($" :force {(cliArgs.HasFlag("force") ? "true" : "false")}");
- Console.WriteLine($" :repro {(cliArgs.HasFlag("repro") ? "true" : "false")}");
- Console.WriteLine($" :main - aliases {cliArgs.GetCommandAlias(EMode.Main)}");
- Console.WriteLine($" :repl - aliases {cliArgs.GetCommandAlias(EMode.Repl)}");
- Console.WriteLine($" :exec - aliases {cliArgs.GetCommandAlias(EMode.Exec)}");
- Console.WriteLine("}");
- }
- else if (cliArgs.HasFlag("tree"))
- { /* already done */ }
- else if (cliArgs.HasFlag("trace"))
+static Process CreateClojureProcess(string installDir, string[] args, Dictionary env)
+{
+ Process process = new()
+ {
+ StartInfo =
{
- Console.WriteLine("Wrote trace.edn"); ;
+ UseShellExecute = false,
+ CreateNoWindow = false,
+ FileName = Platform.IsWindows ? "powershell.exe" : "bash",
}
- else
- {
- //if (Test - Path $JvmFile) {
- // $JvmCacheOpts = @(Get - Content $JvmFile)
+ };
- if (cliArgs.Mode == EMode.Exec || cliArgs.Mode == EMode.Tool)
- {
- // & $JavaCmd -XX:-OmitStackTraceInFastThrow @JavaOpts @JvmCacheOpts @JvmOpts "-Dclojure.basis=$BasisFile" -classpath "$CP;$InstallDir/exec.jar" clojure.main -m clojure.run.exec @ClojureArgs
-
- Console.WriteLine("Starting exec/tool");
+ foreach (var (key, value) in env)
+ process.StartInfo.EnvironmentVariables[key] = value;
- using Process process = new();
- SetInitialProcessParameters(process, installDir);
+ List prependArgs = [];
- var env = process.StartInfo.EnvironmentVariables;
- env["CLOJURE_LOAD_PATH"] = classpath + Path.PathSeparator + installDir; // TODO -- what is this? need to get the equivalant of exec.jar on the load path
- env["clojure.basis"] = basisFile;
- env["clojure.cli.install-dir"] = installDir;
+ if (Platform.IsWindows)
+ prependArgs.AddRange([
+ "-NoProfile",
+ "-ExecutionPolicy", "ByPass",
+ "-File", Path.Join(installDir, "tools", "run-clojure-main.ps1")
+ ]);
+ else
+ prependArgs.Add(Path.Join(installDir, "tools", "run-clojure-main.sh"));
- var argList = process.StartInfo.ArgumentList;
- argList.Add("-m");
- argList.Add("clojure.run.exec");
+ foreach (var arg in prependArgs.Concat(args))
+ process.StartInfo.ArgumentList.Add(arg.Contains(' ') ? $"\"{arg}\"" : arg);
- cliArgs.CommandArgs.ForEach(arg => argList.Add(arg));
-
- process.Start();
- process.WaitForExit();
- }
- else
- {
- // if (Test - Path $MainFile) {
- // # TODO this seems dangerous
- // $MainCacheOpts = @(Get - Content $MainFile) -replace '"', '\"'
- // }
- var mainCacheOpts = File.Exists(mainFile) ? File.ReadAllLines(mainFile).ToList() : null;
-
- if (cliArgs.CommandArgs.Count > 0 && cliArgs.HasFlag("repl"))
- Warn("WARNING: Implicit use of clojure.main with options is deprecated, use -M");
-
- // & $JavaCmd - XX:-OmitStackTraceInFastThrow @JavaOpts @JvmCacheOpts @JvmOpts -Dclojure.basis=$BasisFile -classpath $CP clojure.main @MainCacheOpts @ClojureArgs
-
-
- //using Process process = new();
- //process.StartInfo.UseShellExecute = false;
- //process.StartInfo.FileName = "powershell.exe";
- //process.StartInfo.CreateNoWindow = false; // TODO: When done debugging, set to true
- //process.StartInfo.WorkingDirectory = Environment.CurrentDirectory;
- //var env = process.StartInfo.EnvironmentVariables;
- //env["CLOJURE_LOAD_PATH"] = classpath; // TODO -- what is this?
- //env["clojure.basis"] = basisFile; // will this do -Dclojure.basis=$BasisFile ?
- //var argList = process.StartInfo.ArgumentList;
- //argList.Add(Path.Join(installDir, Path.Join("tools", "run-clojure-main.ps1")));
- //argList.Add("-e");
- //argList.Add("\'(println 12)(load \\\"hello\\\")(println (hello/run))\'");
- //process.Start();
- //process.WaitForExit();
-
- Console.WriteLine("Starting main");
-
- using Process process = new();
- SetInitialProcessParameters(process, installDir);
-
- process.StartInfo.WorkingDirectory = Environment.CurrentDirectory;
-
- var env = process.StartInfo.EnvironmentVariables;
- env["CLOJURE_LOAD_PATH"] = classpath; // TODO -- what is this?
- env["clojure.basis"] = basisFile; // will this do -Dclojure.basis=$BasisFile ?
-
- var argList = process.StartInfo.ArgumentList;
- mainCacheOpts?.ForEach(arg => argList.Add(arg.Replace("\"", "\\\"")));
- cliArgs.CommandArgs.ForEach(arg => argList.Add(arg));
-
- process.Start();
- process.WaitForExit();
- }
- }
-
- return 0;
- }
-
- private static void SetInitialProcessParameters(Process process, string installDir)
- {
- process.StartInfo.UseShellExecute = false;
- process.StartInfo.CreateNoWindow = false; // TODO: When done debugging, set to true
-
- if (IsWindows)
- {
- process.StartInfo.FileName = "powershell.exe";
- }
- else
- {
- process.StartInfo.FileName = "bash";
- }
-
-
- var argList = process.StartInfo.ArgumentList;
-
- if (IsWindows)
- {
- argList.Add(Path.Join(installDir, Path.Join("tools", "run-clojure-main.ps1")));
- }
- else
- {
- argList.Add(Path.Join(installDir, Path.Join("tools", "run-clojure-main.sh")));
- }
- }
+ return process;
}
-