diff --git a/Antlr4BuildTasks/Antlr4BuildTasks.targets b/Antlr4BuildTasks/Antlr4BuildTasks.targets
index f5690e2e..38f4448b 100755
--- a/Antlr4BuildTasks/Antlr4BuildTasks.targets
+++ b/Antlr4BuildTasks/Antlr4BuildTasks.targets
@@ -123,6 +123,7 @@
USERPROFILE/.node/
true
+
@@ -169,6 +170,7 @@
NodeDownloadDirectory="%(Antlr4.NodeDownloadDirectory)"
AntlrNgPath="%(Antlr4.AntlrNgPath)"
Visitor="%(Antlr4.Visitor)"
+ WorkingDirectory="%(Antlr4.WorkingDirectory)"
>
diff --git a/Antlr4BuildTasks/Tasks/RunAntlrTool.cs b/Antlr4BuildTasks/Tasks/RunAntlrTool.cs
index f6ccb891..46437856 100755
--- a/Antlr4BuildTasks/Tasks/RunAntlrTool.cs
+++ b/Antlr4BuildTasks/Tasks/RunAntlrTool.cs
@@ -120,6 +120,7 @@ [Output] public ITaskItem[] GeneratedDirectories
public string NodeExec { get; set; }
public string NodeDownloadDirectory { get; set; }
public string AntlrNgPath { get; set; }
+ public string WorkingDirectory { get; set; }
public async System.Threading.Tasks.Task DownloadFileAsync(string uri, string outputPath)
{
@@ -219,7 +220,8 @@ public override bool Execute()
Package = Package,
DOptions = DOptions,
TreatWarningsAsErrors = Error,
- ForceATN = ForceAtn
+ ForceATN = ForceAtn,
+ WorkingDirectory = WorkingDirectory
};
tool = ngTool;
@@ -253,7 +255,8 @@ public override bool Execute()
Package = Package,
DOptions = DOptions,
TreatWarningsAsErrors = Error,
- ForceATN = ForceAtn
+ ForceATN = ForceAtn,
+ WorkingDirectory = WorkingDirectory
};
tool = javaTool;
diff --git a/Antlr4BuildTasks/Tools/AntlrNgTool.cs b/Antlr4BuildTasks/Tools/AntlrNgTool.cs
index 8ead1400..db5656b6 100644
--- a/Antlr4BuildTasks/Tools/AntlrNgTool.cs
+++ b/Antlr4BuildTasks/Tools/AntlrNgTool.cs
@@ -189,8 +189,8 @@ private List BuildGenerationArguments(IEnumerable grammarFiles)
// Add separator to prevent grammar files from being interpreted as option values
arguments.Add("--");
- // Grammar files
- arguments.AddRange(grammarFiles.Select(NormalizePath));
+ // Grammar files - use relative path so "Generated from" comment is stable
+ arguments.AddRange(grammarFiles.Select(MakeGrammarRelativePath));
return arguments;
}
diff --git a/Antlr4BuildTasks/Tools/AntlrToolBase.cs b/Antlr4BuildTasks/Tools/AntlrToolBase.cs
index 2f993e9c..a13e6c58 100644
--- a/Antlr4BuildTasks/Tools/AntlrToolBase.cs
+++ b/Antlr4BuildTasks/Tools/AntlrToolBase.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -31,6 +32,7 @@ protected AntlrToolBase()
public string DOptions { get; set; }
public bool TreatWarningsAsErrors { get; set; }
public bool ForceATN { get; set; }
+ public string WorkingDirectory { get; set; }
public abstract bool Setup();
@@ -89,6 +91,11 @@ protected bool ExecuteProcess(
RedirectStandardError = true,
};
+ if (ShouldUseWorkingDirectory(WorkingDirectory))
+ {
+ startInfo.WorkingDirectory = WorkingDirectory;
+ }
+
MessageQueue.EnqueueMessage(Message.BuildInfoMessage(
$"Executing command: \"{startInfo.FileName}\" {startInfo.Arguments}"));
@@ -133,6 +140,28 @@ protected string NormalizePath(string path)
return path?.Replace("\\", "/");
}
+ ///
+ /// Converts an absolute grammar file path to be relative to WorkingDirectory.
+ /// If WorkingDirectory is not set or does not exist, returns the path as-is (normalized).
+ ///
+ protected string MakeGrammarRelativePath(string grammarFile)
+ {
+ if (string.IsNullOrEmpty(grammarFile) || !ShouldUseWorkingDirectory(WorkingDirectory))
+ return NormalizePath(grammarFile);
+
+ var fullPath = Path.GetFullPath(grammarFile);
+ var workDir = Path.GetFullPath(WorkingDirectory);
+ if (!workDir.EndsWith(Path.DirectorySeparatorChar.ToString()))
+ workDir += Path.DirectorySeparatorChar;
+
+ if (fullPath.StartsWith(workDir, StringComparison.OrdinalIgnoreCase))
+ {
+ return NormalizePath(fullPath.Substring(workDir.Length));
+ }
+
+ return NormalizePath(grammarFile);
+ }
+
///
/// Splits a semicolon-separated list and filters empty entries
///
@@ -145,5 +174,13 @@ protected IEnumerable SplitAndFilterList(string list)
.Select(s => s.Trim())
.Where(s => !string.IsNullOrWhiteSpace(s));
}
+
+ ///
+ /// Returns true if WorkingDirectory is set and the directory exists on disk.
+ ///
+ private static bool ShouldUseWorkingDirectory(string workingDirectory)
+ {
+ return !string.IsNullOrEmpty(workingDirectory) && Directory.Exists(workingDirectory);
+ }
}
}
diff --git a/Antlr4BuildTasks/Tools/IAntlrTool.cs b/Antlr4BuildTasks/Tools/IAntlrTool.cs
index 8b6b6670..9657ba14 100644
--- a/Antlr4BuildTasks/Tools/IAntlrTool.cs
+++ b/Antlr4BuildTasks/Tools/IAntlrTool.cs
@@ -98,5 +98,12 @@ bool DiscoverGeneratedFiles(
/// Gets or sets whether to force ATN for all decisions
///
bool ForceATN { get; set; }
+
+ ///
+ /// Gets or sets the working directory for the tool process.
+ /// When set, grammar file paths are converted to be relative to this directory
+ /// so that the "Generated from" comment in output files uses relative paths.
+ ///
+ string WorkingDirectory { get; set; }
}
}
diff --git a/Antlr4BuildTasks/Tools/JavaAntlrTool.cs b/Antlr4BuildTasks/Tools/JavaAntlrTool.cs
index 407a8cd8..36313664 100644
--- a/Antlr4BuildTasks/Tools/JavaAntlrTool.cs
+++ b/Antlr4BuildTasks/Tools/JavaAntlrTool.cs
@@ -145,8 +145,8 @@ private List BuildDependencyArguments(string grammarFile)
// Common arguments
AddCommonArguments(grammarFile.EndsWith("Parser.g4"), arguments);
- // Grammar files
- arguments.Add(grammarFile);
+ // Grammar files - use relative path so "Generated from" comment is stable
+ arguments.Add(MakeGrammarRelativePath(grammarFile));
return arguments;
}
@@ -167,8 +167,8 @@ private List BuildGenerationArguments(string grammarFile)
if (EnableLogging)
arguments.Add("-Xlog");
- // Grammar files
- arguments.Add(grammarFile);
+ // Grammar files - use relative path so "Generated from" comment is stable
+ arguments.Add(MakeGrammarRelativePath(grammarFile));
return arguments;
}