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
170
171
172
173
174
175
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using OpenTap.Cli;
using OpenTap.Package;
 
namespace OpenTap.Sdk.New
{
    [Display("package-reference", "Add an OpenTAP Package to a C# Project (.csproj).", Groups: new[] { "sdk", "new" })]
    public class NewPackageReference : ICliAction
    {
        [UnnamedCommandLineArgument("Package Name", Description = "The name of the package to reference.")]
        public string PackageName { get; set; }
        [CommandLineArgument("version", Description = "Version of the package (newest version if not set).")]
        public string Version { get; set; }
        
        [CommandLineArgument("project", Description = "C# project file to add the package reference to. If nothing is selected the csproj of the current directiory (if present) is selected.")]
        public string Project { get; set; }
 
        [CommandLineArgument("repository",
            Description = "The package repository to use (default: packages.opentap.io)")]
        public string PackageRepository { get; set; } = "https://packages.opentap.io";
 
        [CommandLineArgument("no-reference",
            Description = "Specify if the assemblies in the package should not be referenced by the project.")]
        public bool NoReference { get; set; }
        
        [CommandLineArgument("configuration", Description = "In which build configuration this should be used. The default is all configurations.")]
        public string Configuration { get; set; }
 
        static TraceSource log = Log.CreateSource("sdk");
        public int Execute(CancellationToken cancellationToken)
        {
            if (string.IsNullOrEmpty(PackageName))
            {
                log.Error("Please specify a valid package name.");
                return (int)ExitCodes.ArgumentError;
            }
 
            {   // check if the package exists in the repo.
                var repo = new HttpPackageRepository(PackageRepository);
                var spec = new PackageSpecifier(PackageName, Version != null ? VersionSpecifier.Parse(Version) : null);
                var pkg = repo.GetPackages(spec, TapThread.Current.AbortToken).FirstOrDefault();
                if (pkg == null)
                {
                    log.Error("No such package");
                    return (int)ExitCodes.ArgumentError;
                }
                if (Version == null)
                    Version = pkg.Version.ToString();
                PackageName = pkg.Name;
            }
 
            string ifRefStr = "";
            if (NoReference)
                ifRefStr = " Reference=\"false\"";
            
            string insert =
                $"<OpenTapPackageReference Include=\"{PackageName}\" Version=\"{Version}\" Repository=\"{PackageRepository}\"{ifRefStr}/>";
            
            var csproj = Project ?? get_csproj();
            if (csproj == null)
            {  // error was already printed.
                return (int)ExitCodes.ArgumentError;
            }
 
            if (File.Exists(csproj) == false)
            {
                log.Error("C# project files does not exist {0}", csproj);
                return (int)ExitCodes.ArgumentError;
            }
            
            var document = XDocument.Load(csproj, LoadOptions.PreserveWhitespace);
            var projectXml = document.Element("Project");
            XElement itemGroupXml = null;
            var condition = $"'$(Configuration)' == '{Configuration}'";
            
            var itemGroups = projectXml.Elements("ItemGroup");
            
            // if condition is select take the group matching the conditions.
            if (string.IsNullOrWhiteSpace(Configuration) == false)
                itemGroups = itemGroups.Where(grp => grp.Attribute("Condition")?.Value == condition);
            
            bool needsAddNewElem = true; 
            
            foreach (var grp in itemGroups)
            {
                var existingElem = grp.Elements("OpenTapPackageReference")
                    .Where(elem => string.Equals(elem.Attribute("Reference")?.Value ?? "True", (!NoReference).ToString(), StringComparison.OrdinalIgnoreCase))
                    .FirstOrDefault(elem => elem.Attribute("Include")?.Value == PackageName);
                
                if (existingElem != null)
                {  // the package reference already exists. In this case, lets try to just update set the version.
                    
                    var version = existingElem.Attribute("Version");
                    if (version != null)
                    {
                        if (version.Value == Version)
                        {
                            log.Info("Package {0} version {1} already in the csproj.", PackageName, Version);
                            return (int)ExitCodes.Success;
                        }
 
                        log.Info("Package {0} version {1} changed to version {2}.", PackageName, version.Value, Version);
                        
                        version.Value = Version;
                        needsAddNewElem = false;
                        break;
                    }
                }
            }
 
            if (needsAddNewElem)
            {
                // add a new OpenTapPackageReference or AdditionalOpenTapPackage element.
                // to make the whitespace look right, there is some adding additional whitespace
                
                // Try to find the existing item group used for package references.
                foreach (var grp in itemGroups)
                {
                    if (grp.Elements("OpenTapPackageReference").Any())
                    {
                        itemGroupXml = grp;
                        break;
                    }
                }
                
                if (itemGroupXml == null)
                {
                    itemGroupXml = new XElement("ItemGroup");
                    if (string.IsNullOrWhiteSpace(Configuration) == false)
                    {
                        // e.g Condition="'{Configuration}' == 'Debug'"
                        itemGroupXml.Add(new XAttribute("Condition", condition));
                    }
                    itemGroupXml.Add("\n");
                    itemGroupXml.Add("   ");
                    projectXml.Add("\n");
                    projectXml.Add("   ");
                    projectXml.Add(itemGroupXml);
                    projectXml.Add("\n");
                }
 
                itemGroupXml.Add("   ");
                itemGroupXml.Add(XElement.Parse(insert));
                itemGroupXml.Add("\n");
                itemGroupXml.Add("   ");
                log.Info("Package {0} version {1} reference added to the project.", PackageName, Version);
            }
 
            document.Save(csproj, SaveOptions.DisableFormatting);
            return (int)ExitCodes.Success;
        }
 
        static string get_csproj()
        {
            var csprojFiles = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.csproj").ToArray();
            if (csprojFiles.Length == 0)
            {
                log.Error("Unable to find any csproj file in the current directory. Use --csproj to specify.");
                return null;
            }
 
            if (csprojFiles.Length > 1)
            {
                log.Error("Directory contains multiple csproj files, please specify using --csproj");
                return null;
            }
 
            return csprojFiles[0];
        }
    }
}