Skip to content

DYN-10278-Fix C3D AppData Extra Version Folder#16951

Open
RobertGlobant20 wants to merge 10 commits intoDynamoDS:masterfrom
RobertGlobant20:Fix-C3D-AppData-ExtraVersionFolder-Creation
Open

DYN-10278-Fix C3D AppData Extra Version Folder#16951
RobertGlobant20 wants to merge 10 commits intoDynamoDS:masterfrom
RobertGlobant20:Fix-C3D-AppData-ExtraVersionFolder-Creation

Conversation

@RobertGlobant20
Copy link
Contributor

@RobertGlobant20 RobertGlobant20 commented Mar 9, 2026

Purpose

Added a method that will validate if an assembly versioned exists and will create only one version folder (host version) in a specific path based in the host version:

  • Revit - C:\Users<user>\AppData\Roaming\Dynamo\Dynamo Revit or
  • Civil3D - C:\Users<user>\AppData\Roaming\Autodesk\C3D 2027\Dynamo

Declarations

Check these if you believe they are true

Release Notes

Added a method that will validate if an assembly versioned exists and will create only one version folder (host version) in s specific path based in the host

Reviewers

@benglin
@zeusongit

FYIs

Added a method that will validate if an assembly versioned exists and will create only one version folder (host version) in:
C:\Users\tellro\AppData\Roaming\Dynamo\Dynamo Revit
or
C:\Users\tellro\AppData\Roaming\Autodesk\C3D 2027\Dynamo
Updating TraverseForExecutableAssembly() method description
@RobertGlobant20 RobertGlobant20 requested a review from benglin March 9, 2026 18:42
@RobertGlobant20 RobertGlobant20 marked this pull request as ready for review March 10, 2026 14:24
Copilot AI review requested due to automatic review settings March 10, 2026 14:24
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates PathManager’s version-folder derivation to avoid creating extra AppData version directories in hosted scenarios (e.g., Civil3D/Revit) by discovering a host-facing assembly version at runtime.

Changes:

  • Removes the direct DynamoCore.dll-path validation previously used during PathManager construction.
  • Introduces call-stack traversal to find a “host-facing” assembly with a non-zero file version and uses it to set majorFileVersion/minorFileVersion when not provided.
Comments suppressed due to low confidence (3)

src/DynamoCore/Configuration/PathManager.cs:493

  • This change alters how majorFileVersion/minorFileVersion are derived (now potentially host/integration assembly based), but the PR doesn’t include corresponding unit test updates/additions. There are existing tests that assert the versioned folder suffix (e.g., expecting ...\4.1 for a Revit resolver), and this new logic should be covered with tests for (1) hosted resolver case uses the intended host version, and (2) test/sandbox scenarios don’t accidentally pick a dependency assembly from the call stack.
            // Go up the call-stack to look for a versioned assembly,
            // and we will likely discover 'AcDynamo.dll' that is '18.0'.
            var assemblyPath = TraverseForExecutableAssembly();

            // If both major/minor versions are zero, get from assembly.
            majorFileVersion = pathManagerParams.MajorFileVersion;
            minorFileVersion = pathManagerParams.MinorFileVersion;
            if (majorFileVersion == 0 && (minorFileVersion == 0))
            {
                var v = FileVersionInfo.GetVersionInfo(assemblyPath);
                majorFileVersion = v.FileMajorPart;
                minorFileVersion = v.FileMinorPart;
            }

src/DynamoCore/Configuration/PathManager.cs:477

  • The constructor no longer validates that corePath actually contains DynamoCore.dll. Previously this threw a clear exception when the core path was misconfigured; now a directory that exists but lacks the core assembly will be accepted and may fail later in less actionable ways. Consider restoring the DynamoCore.dll existence/valid-path check (using dynamoCoreDir) while keeping the host-version discovery logic separate.
            if (string.IsNullOrEmpty(corePath) || !Directory.Exists(corePath))
            {
                // If the caller does not provide an alternative core path, 
                // use the default folder in which DynamoCore.dll resides.
                var dynamoCorePath = Assembly.GetExecutingAssembly().Location;
                corePath = Path.GetDirectoryName(dynamoCorePath);
            }

            dynamoCoreDir = corePath;

            extensionsDirectories = new HashSet<string>();
            viewExtensionsDirectories = new HashSet<string>();

            extensionsDirectories.Add(Path.Combine(dynamoCoreDir, ExtensionsDirectoryName));
            viewExtensionsDirectories.Add(Path.Combine(dynamoCoreDir, ViewExtensionsDirectoryName));

src/DynamoCore/Configuration/PathManager.cs:490

  • TraverseForExecutableAssembly() is called unconditionally, even when pathManagerParams.MajorFileVersion/MinorFileVersion are provided (non-zero) and no version discovery is needed. Since this builds a StackTrace, it’s avoidable overhead in the common case; consider moving the call inside the (majorFileVersion == 0 && minorFileVersion == 0) block and only computing assemblyPath when required.
            // Go up the call-stack to look for a versioned assembly,
            // and we will likely discover 'AcDynamo.dll' that is '18.0'.
            var assemblyPath = TraverseForExecutableAssembly();

            // If both major/minor versions are zero, get from assembly.
            majorFileVersion = pathManagerParams.MajorFileVersion;
            minorFileVersion = pathManagerParams.MinorFileVersion;
            if (majorFileVersion == 0 && (minorFileVersion == 0))
            {
                var v = FileVersionInfo.GetVersionInfo(assemblyPath);

Comment on lines +518 to +543
foreach (var frame in stackTrace.GetFrames() ?? Array.Empty<StackFrame>())
{
var assembly = frame.GetMethod()?.DeclaringType?.Assembly;
if (assembly == null || assembly == currentAssembly || assembly.IsDynamic)
continue;

var assemblyName = assembly.GetName().Name;
if (string.IsNullOrEmpty(assemblyName))
continue;

if (assemblyName.StartsWith("System", StringComparison.OrdinalIgnoreCase) ||
assemblyName.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase) ||
assemblyName.Equals("mscorlib", StringComparison.OrdinalIgnoreCase) ||
assemblyName.Equals("netstandard", StringComparison.OrdinalIgnoreCase) ||
assemblyName.IndexOf("test", StringComparison.OrdinalIgnoreCase) >= 0)
{
continue;
}

var candidatePath = assembly.Location;
if (PathHelper.IsValidPath(candidatePath))
{
var fileVersion = FileVersionInfo.GetVersionInfo(candidatePath);
if (fileVersion.FileMajorPart > 0 || fileVersion.FileMinorPart > 0)
return candidatePath;
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stack-walk selection criteria can return an unrelated non-system assembly (e.g., nunit.framework, Moq, other add-ins) as soon as it appears on the call stack, which would cause the data-folder version to be derived from that dependency rather than the host application/integration assembly. This makes the version folder non-deterministic across environments. Consider tightening the selection to something host-specific (for example: prefer an assembly whose Location is under hostApplicationDirectory, or fall back to Assembly.GetEntryAssembly() / process main module when hosted), and only use the stack trace as a last resort.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was re-implemented so the comment is not valid anymore

Comment on lines +501 to +511
/// Traverses the current call stack to identify the first host-facing assembly
/// with a non-zero file version, and returns its path for version discovery.
///
/// Starting from Dynamo 4.0, data folders are no longer versioned based on
/// DynamoCore.dll (e.g. '/4.0/data'). Instead, they are versioned according to
/// the host application's major version (e.g. '/27.0/data' for Revit 2027).
/// As a result, we must inspect the call stack to locate the host assembly
/// rather than relying on DynamoCore's assembly version.
/// </summary>
/// <returns>
/// The file path of the host assembly used to determine the data directory version.
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML doc comment states the data folder is versioned by the “host application's major version (e.g. '/27.0/data' for Revit 2027)”, but the implementation actually uses the first non-filtered assembly on the managed call stack with a non-zero file version. That may be the host integration DLL (or even a third-party library) and its file version may not match the host app major version. Please align the documentation with the actual behavior, or adjust the selection logic so it reliably derives the host application version as described.

Suggested change
/// Traverses the current call stack to identify the first host-facing assembly
/// with a non-zero file version, and returns its path for version discovery.
///
/// Starting from Dynamo 4.0, data folders are no longer versioned based on
/// DynamoCore.dll (e.g. '/4.0/data'). Instead, they are versioned according to
/// the host application's major version (e.g. '/27.0/data' for Revit 2027).
/// As a result, we must inspect the call stack to locate the host assembly
/// rather than relying on DynamoCore's assembly version.
/// </summary>
/// <returns>
/// The file path of the host assembly used to determine the data directory version.
/// Traverses the current managed call stack to identify the first non-filtered
/// assembly with a non-zero file version, and returns its path for version
/// discovery.
///
/// Starting from Dynamo 4.0, data folders are no longer versioned based solely on
/// DynamoCore.dll (for example, <c>"/4.0/data"</c>). Instead, the file version of
/// a host-facing assembly on the call stack (typically an integration assembly
/// such as <c>AcDynamo.dll</c>) is used as a proxy for the host application's
/// version when constructing versioned data folders (for example,
/// <c>"/27.0/data"</c> for a host corresponding to version 27.0).
///
/// Host integrations that rely on this behavior are expected to ensure that the
/// selected assembly's file version matches the intended host application version.
/// This method does not directly inspect the host executable itself; it simply
/// returns the first suitable assembly discovered on the call stack.
/// </summary>
/// <returns>
/// The file path of the assembly whose file version is used to determine the
/// versioned data directory.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this comment was updated in the last commit so this comment is not valid any more

@RobertGlobant20 RobertGlobant20 requested review from reddyashish and zeusongit and removed request for reddyashish March 10, 2026 19:10
RobertGlobant20 and others added 3 commits March 11, 2026 12:36
TraverseForExecutableAssembly() to review assemblies in specific order.
 /// 1) First assembly on the current managed call stack under hostApplicationDirectory.
        /// 2) Entry assembly location.
        /// 3) Current process main module path.
        /// 4) First non-system, non-test assembly on the current managed call stack.
        /// 5) Fallback to DynamoCore assembly location.
Change in TraverseForExecutableAssembly() to review assemblies in specific order.
@RobertGlobant20
Copy link
Contributor Author

This s GIF showing the expected behavior for Civil3D (the first time is creating the same folder than the second time is launched).
acad_n3pV0VuJDJ

@RobertGlobant20
Copy link
Contributor Author

GIF showing the expected behavior (the first time Revit is launched and the second time is launched is creating one folder).
Revit_b4Q3FA564G

@RobertGlobant20 RobertGlobant20 changed the title Fix C3D AppData Extra Version Folder DYN-10278-Fix C3D AppData Extra Version Folder Mar 12, 2026
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the ticket for this pull request: https://jira.autodesk.com/browse/DYN-10278

@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants