chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
using System;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
 
namespace OpenTap.Package
{
    [Display("Include Package Dependencies")]
    class IncludePackageDependencies : ICustomPackageAction
    {
        /// <summary>
        /// Defines the IncludePackageDependencies XML element that indicates that the package should inherit plugin dependencies from this file.
        /// </summary>
        [Display("IncludePackageDependencies")]
        class IncludePackageDependenciesData : ICustomPackageData
        {
 
        }
        /// <summary>
        /// It doesn't really matter when this is run.
        /// </summary>
        /// <returns></returns>
        public int Order() => 10;
 
        public PackageActionStage ActionStage => PackageActionStage.Create;
        private static TraceSource log = Log.CreateSource(nameof(IncludePackageDependencies));
 
        public bool Execute(PackageDef pkgDef, CustomPackageActionArgs customActionArgs)
        {
            // Add all package dependencies specified in an xml file with the following hierarchy to 'pkgDef'
            // <Package.Dependencies>
            //     <Package Name="a" Version="b" />    
            // </Package.Dependencies>
            bool success = true;
            foreach (var item in pkgDef.Files)
            {
                // Dependencies should not be included from this file -- skip
                if (item.HasCustomData<IncludePackageDependenciesData>() == false)
                    continue;
 
                XDocument document;
                try
                {
                    document = XDocument.Load(item.FileName, LoadOptions.SetLineInfo | LoadOptions.PreserveWhitespace);
                }
                catch
                {
                    log.Error($"File '{item.FileName}' is not a valid XML document.");
                    success = false;
                    continue;
                }
 
                var selector = "//Package.Dependencies/Package";
 
                var nodes = document.XPathSelectElements(selector).ToArray();
 
                // No dependencies specified in the file -- skip
                if (nodes.Length == 0)
                    continue;
 
                log.Info($"Inheriting package dependencies from {item.FileName}:");
 
                string AttributeError(string errorMessage, XElement ele)
                {
                    string details;
                    if (ele is IXmlLineInfo l && l.HasLineInfo())
                        details = $"({ele} - line {l.LineNumber})";
                    else
                        details = $"({ele})";
 
                    var msg = $"{errorMessage} {details}";
 
                    return msg;
                }
 
                foreach (var node in nodes)
                {
                    if (!(node is XElement ele)) continue;
                    
                    var thisName = ele.Attribute("Name")?.Value;
 
                    if (string.IsNullOrWhiteSpace(thisName))
                    {
                        log.Error(AttributeError("Attribute 'Name' is not set.", ele));
                        success = false;
                        continue;
                    }
 
                    var thisVersion = ele.Attribute("Version")?.Value;
 
                    VersionSpecifier thisVersionSpec;
                    SemanticVersion thisSemver;
                    var thisAny = string.IsNullOrWhiteSpace(thisVersion) ||
                                  thisVersion.Equals("any", StringComparison.OrdinalIgnoreCase);
 
                    try
                    {
                        if (thisAny)
                        {
                            thisVersionSpec = VersionSpecifier.Any;
                            thisVersion = "any";
                            thisSemver = null;
                        }
                        else
                        {
                            if (thisVersion.StartsWith("^"))
                            {
                                thisVersionSpec = VersionSpecifier.Parse(thisVersion);
                                thisSemver = SemanticVersion.Parse(thisVersion.Substring(1));
                            }
                            else
                            {
                                thisVersionSpec = VersionSpecifier.Parse("^" + thisVersion);
                                thisSemver = SemanticVersion.Parse(thisVersion);
                            }
                        }
                    }
                    catch
                    {
                        log.Error("Attribute 'Version' is not a valid version specifier.", ele);
                        success = false;
                        continue;
                    }
 
                    var thisDep = new PackageDependency(thisName, thisVersionSpec, thisVersion);
 
                    // Avoid adding duplicate entries
                    var existing = pkgDef.Dependencies.FirstOrDefault(d => d.Name == thisName);
 
                    // Determine if we should replace the existing version
                    if (existing != null)
                    {
                        var otherAny =
                            existing.RawVersion.Equals("any", StringComparison.OrdinalIgnoreCase);
 
                        var otherSemver = otherAny ? null : SemanticVersion.Parse(existing.RawVersion);
 
                        // If the existing dependency is compatible with this one, we can safely ignore it
                        if (thisAny || (otherAny == false && thisVersionSpec.IsCompatible(otherSemver))) continue;
 
                        // If the new version is compatible with the previous one, we can safely replace the previous version.
                        if (existing.Version.IsCompatible(thisSemver) || otherAny)
                        {
                            pkgDef.Dependencies.Remove(existing);
                            pkgDef.Dependencies.Add(thisDep);
                        }
                        // Otherwise the two versions are in conflict. This error is not recoverable
                        else
                        {
                            log.Error($"Dependency conflict: {thisName} v. '{thisVersion}' and '{existing.RawVersion}' are mutually exclusive.");
                            success = false;
                            continue;
                        }
                    }
                    // Otherwise add a new dependency for this element
                    else
                    {
                        pkgDef.Dependencies.Add(thisDep);
                    }
                }
 
                item.RemoveCustomData<IncludePackageDependenciesData>();
            }
 
            return success;
        }
    }
}