edacation 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,11 +9,37 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.Project = exports.ProjectOutputFile = exports.ProjectInputFile = void 0;
12
+ exports.Project = exports.ProjectTarget = exports.ProjectOutputFile = exports.ProjectInputFile = void 0;
13
13
  const util_js_1 = require("../util.js");
14
14
  const configuration_js_1 = require("./configuration.js");
15
+ const devices_js_1 = require("./devices.js");
16
+ const flasher_js_1 = require("./flasher.js");
17
+ const iverilog_js_1 = require("./iverilog.js");
18
+ const nextpnr_js_1 = require("./nextpnr.js");
19
+ const target_js_1 = require("./target.js");
20
+ const yosys_js_1 = require("./yosys.js");
21
+ const guessInputFileType = (filePath) => {
22
+ // *_tb.<hdl_ext> -> testbench
23
+ // *.<pin_cfg_ext> -> pinconfig
24
+ // default: design
25
+ const baseName = filePath.split('/').pop() ?? filePath;
26
+ const lowerBase = baseName.toLowerCase();
27
+ const ext = lowerBase.split('.').pop() ?? '';
28
+ const hdlExts = util_js_1.FILE_EXTENSIONS_HDL.map(e => e.toLowerCase());
29
+ const pincfgExts = util_js_1.FILE_EXTENSIONS_PINCFG.map(e => e.toLowerCase());
30
+ if (hdlExts.some(e => lowerBase.endsWith(`_tb.${e}`)))
31
+ return 'testbench';
32
+ if (pincfgExts.includes(ext))
33
+ return 'pinconfig';
34
+ return 'design';
35
+ };
36
+ // Fallback clone to handle Proxy objects (structuredClone throws DataCloneError on Proxy)
37
+ const safeStructuredClone = (value) => {
38
+ return JSON.parse(JSON.stringify(value));
39
+ };
15
40
  class ProjectInputFile {
16
- constructor(_path, _type) {
41
+ constructor(_project, _path, _type) {
42
+ this._project = _project;
17
43
  this._path = _path;
18
44
  this._type = _type;
19
45
  }
@@ -24,7 +50,10 @@ class ProjectInputFile {
24
50
  return this._type;
25
51
  }
26
52
  set type(type) {
53
+ if (this._type === type)
54
+ return;
27
55
  this._type = type;
56
+ this._project.triggerInputFilesChanged();
28
57
  }
29
58
  serialize() {
30
59
  return {
@@ -32,16 +61,15 @@ class ProjectInputFile {
32
61
  type: this.type
33
62
  };
34
63
  }
35
- static deserialize(data, ..._args) {
36
- // Older versions of this module (<= 0.3.9) stored input files as an array of paths instead,
37
- // so we need to migrate if data is a string (single output file).
64
+ static deserialize(project, data, ..._args) {
65
+ // Older versions (<= 0.3.9) stored input files as an array of paths
38
66
  if (typeof data === 'string') {
39
67
  data = { path: data, type: 'design' };
40
68
  }
41
- return new ProjectInputFile(data.path, data.type);
69
+ return new ProjectInputFile(project, data.path, data.type);
42
70
  }
43
- copy() {
44
- return ProjectInputFile.deserialize(this.serialize());
71
+ copy(project) {
72
+ return ProjectInputFile.deserialize(project, this.serialize());
45
73
  }
46
74
  }
47
75
  exports.ProjectInputFile = ProjectInputFile;
@@ -62,7 +90,10 @@ class ProjectOutputFile {
62
90
  if (id !== null && this._project.getTarget(id) === null) {
63
91
  throw new Error(`Invalid target id: ${id}`);
64
92
  }
93
+ if (this._targetId === id)
94
+ return;
65
95
  this._targetId = id;
96
+ this._project.triggerOutputFilesChanged();
66
97
  }
67
98
  get target() {
68
99
  if (!this._targetId)
@@ -73,7 +104,10 @@ class ProjectOutputFile {
73
104
  return this._stale;
74
105
  }
75
106
  set stale(isStale) {
107
+ if (this._stale === isStale)
108
+ return;
76
109
  this._stale = isStale;
110
+ this._project.triggerOutputFilesChanged();
77
111
  }
78
112
  serialize() {
79
113
  return {
@@ -83,8 +117,7 @@ class ProjectOutputFile {
83
117
  };
84
118
  }
85
119
  static deserialize(project, data, ..._args) {
86
- // Older versions of this module (<= 0.3.12) stored output files as an array of paths instead,
87
- // so we need to migrate if data is a string (single output file).
120
+ // Older versions (<= 0.3.12) stored output files as an array of paths
88
121
  if (typeof data === 'string') {
89
122
  data = { path: data, targetId: null, stale: false };
90
123
  }
@@ -95,21 +128,195 @@ class ProjectOutputFile {
95
128
  }
96
129
  }
97
130
  exports.ProjectOutputFile = ProjectOutputFile;
131
+ class ProjectTarget {
132
+ constructor(_project, _data) {
133
+ this._project = _project;
134
+ this._data = _data;
135
+ }
136
+ get id() {
137
+ return this._data.id;
138
+ }
139
+ set id(newId) {
140
+ if (newId === this._data.id)
141
+ return;
142
+ if (this._project.hasTarget(newId)) {
143
+ throw new Error(`Target with ID "${newId}" already exists!`);
144
+ }
145
+ this._data.id = newId;
146
+ this._project.triggerConfigurationChanged();
147
+ }
148
+ get name() {
149
+ return this._data.name;
150
+ }
151
+ set name(newName) {
152
+ if (newName === this._data.name)
153
+ return;
154
+ this._data.name = newName;
155
+ this._project.triggerConfigurationChanged();
156
+ }
157
+ get isActive() {
158
+ const activeTarget = this._project.getActiveTarget();
159
+ return activeTarget?.id === this.id;
160
+ }
161
+ setActive() {
162
+ if (this.isActive)
163
+ return;
164
+ this._project.setActiveTarget(this.id);
165
+ }
166
+ get vendorId() {
167
+ return this._data.vendor;
168
+ }
169
+ get availableVendors() {
170
+ return devices_js_1.VENDORS;
171
+ }
172
+ get vendor() {
173
+ return this.availableVendors[this.vendorId];
174
+ }
175
+ setVendor(vendorId) {
176
+ if (vendorId === this._data.vendor)
177
+ return;
178
+ if (!devices_js_1.VENDORS[vendorId]) {
179
+ throw new Error(`Invalid vendor: ${vendorId}`);
180
+ }
181
+ this._data.vendor = vendorId;
182
+ // Reset family/device/package when changing vendor
183
+ this._data.family = Object.keys(this.availableFamilies)[0];
184
+ this._data.device = Object.keys(this.availableDevices)[0];
185
+ this._data.package = Object.keys(this.availablePackages)[0];
186
+ this._project.triggerConfigurationChanged();
187
+ }
188
+ get familyId() {
189
+ return this._data.family;
190
+ }
191
+ get availableFamilies() {
192
+ return this.vendor?.families || {};
193
+ }
194
+ get family() {
195
+ return this.availableFamilies[this.familyId];
196
+ }
197
+ setFamily(familyId) {
198
+ if (familyId === this._data.family)
199
+ return;
200
+ if (!this.vendor?.families[familyId]) {
201
+ throw new Error(`Invalid family: ${familyId}`);
202
+ }
203
+ this._data.family = familyId;
204
+ // Reset device/package when changing family
205
+ this._data.device = Object.keys(this.availableDevices)[0];
206
+ this._data.package = Object.keys(this.availablePackages)[0];
207
+ this._project.triggerConfigurationChanged();
208
+ }
209
+ get deviceId() {
210
+ return this._data.device;
211
+ }
212
+ get availableDevices() {
213
+ return this.family?.devices || {};
214
+ }
215
+ get device() {
216
+ return this.availableDevices[this.deviceId];
217
+ }
218
+ setDevice(deviceId) {
219
+ if (deviceId === this._data.device)
220
+ return;
221
+ if (!this.family?.devices[deviceId]) {
222
+ throw new Error(`Invalid device: ${deviceId}`);
223
+ }
224
+ this._data.device = deviceId;
225
+ // Reset package when changing device
226
+ this._data.package = Object.keys(this.availablePackages)[0];
227
+ this._project.triggerConfigurationChanged();
228
+ }
229
+ get packageId() {
230
+ return this._data.package;
231
+ }
232
+ get availablePackages() {
233
+ return this.device?.packages.reduce((prev, packageId) => {
234
+ const vendorPackages = devices_js_1.VENDORS[this.vendorId].packages;
235
+ prev[packageId] = vendorPackages[packageId] ?? packageId;
236
+ return prev;
237
+ }, {}) ?? {};
238
+ }
239
+ get package() {
240
+ return this.availablePackages[this.packageId];
241
+ }
242
+ setPackage(packageId) {
243
+ if (packageId === this._data.package)
244
+ return;
245
+ if (!this.device?.packages.includes(packageId)) {
246
+ throw new Error(`Invalid package: ${packageId}`);
247
+ }
248
+ this._data.package = packageId;
249
+ this._project.triggerConfigurationChanged();
250
+ }
251
+ get config() {
252
+ return this._data;
253
+ }
254
+ setConfig(path, value) {
255
+ if (!path.length)
256
+ throw new Error('Path must be a non-empty array');
257
+ let cursor = this._data;
258
+ for (let i = 0; i < path.length - 1; i++) {
259
+ const key = path[i];
260
+ const next = cursor[key];
261
+ if (typeof next !== 'object' || next === null) {
262
+ const child = {};
263
+ cursor[key] = child;
264
+ cursor = child;
265
+ }
266
+ else {
267
+ cursor = next;
268
+ }
269
+ }
270
+ const last = path[path.length - 1];
271
+ if (cursor[last] === value)
272
+ return;
273
+ cursor[last] = value;
274
+ this._project.triggerConfigurationChanged();
275
+ }
276
+ getEffectiveOptions(workerId) {
277
+ if (workerId === 'yosys')
278
+ return (0, yosys_js_1.getYosysOptions)(this._project.getConfiguration(), this.id);
279
+ else if (workerId === 'nextpnr')
280
+ return (0, nextpnr_js_1.getNextpnrOptions)(this._project.getConfiguration(), this.id);
281
+ else if (workerId === 'iverilog')
282
+ return (0, iverilog_js_1.getIVerilogOptions)(this._project.getConfiguration(), this.id);
283
+ else if (workerId === 'flasher')
284
+ return (0, flasher_js_1.getFlasherOptions)(this._project.getConfiguration(), this.id);
285
+ throw new Error(`Worker ID "${String(workerId)}" is not supported.`);
286
+ }
287
+ getEffectiveTextConfig(workerId, configId, generated, parse = target_js_1.defaultParse) {
288
+ return (0, target_js_1.getCombined)(this._project.getConfiguration(), this.id, workerId, configId, generated, parse);
289
+ }
290
+ update(updates) {
291
+ if (updates.id && updates.id !== this.id) {
292
+ if (this._project.hasTarget(updates.id)) {
293
+ throw new Error(`Target with ID "${updates.id}" already exists!`);
294
+ }
295
+ this._data.id = updates.id;
296
+ }
297
+ Object.assign(this._data, safeStructuredClone(updates));
298
+ this._project.triggerConfigurationChanged();
299
+ }
300
+ serialize() {
301
+ return this._data;
302
+ }
303
+ }
304
+ exports.ProjectTarget = ProjectTarget;
98
305
  class Project {
99
306
  constructor(name, inputFiles = [], outputFiles = [], configuration = configuration_js_1.DEFAULT_CONFIGURATION, eventCallback) {
100
307
  this.batchedEvents = new Set();
101
308
  this.batchCounter = 0;
102
309
  this.name = name;
103
- this.inputFiles = inputFiles.map((file) => ProjectInputFile.deserialize(file));
310
+ this.inputFiles = inputFiles.map((file) => ProjectInputFile.deserialize(this, file));
104
311
  this.outputFiles = outputFiles.map((file) => ProjectOutputFile.deserialize(this, file));
105
312
  const config = configuration_js_1.schemaProjectConfiguration.safeParse(configuration);
106
313
  if (config.success) {
107
314
  this.configuration = config.data;
108
315
  }
109
316
  else {
110
- throw new Error(`Failed to parse project configuration: ${config.error.toString()}`);
317
+ throw new Error(`Failed to parse project configuration: ${config.error.message}`);
111
318
  }
112
- // Trigger a config 'update' to deploy any modifications it might want to make
319
+ // Trigger any updates that the configuration might want to do
113
320
  this.updateConfiguration({});
114
321
  // Set event callback LAST to prevent firing events in constructor
115
322
  this.eventCallback = eventCallback;
@@ -117,6 +324,9 @@ class Project {
117
324
  getName() {
118
325
  return this.name;
119
326
  }
327
+ setName(name) {
328
+ this.name = name;
329
+ }
120
330
  getInputFiles() {
121
331
  return this.inputFiles;
122
332
  }
@@ -129,7 +339,8 @@ class Project {
129
339
  addInputFiles(files) {
130
340
  for (const file of files) {
131
341
  if (!this.hasInputFile(file.path)) {
132
- const inputFile = new ProjectInputFile(file.path, file.type ?? 'design');
342
+ const fileType = file.type ?? guessInputFileType(file.path);
343
+ const inputFile = new ProjectInputFile(this, file.path, fileType);
133
344
  this.inputFiles.push(inputFile);
134
345
  }
135
346
  }
@@ -190,16 +401,14 @@ class Project {
190
401
  const target = this.getTarget(targetId);
191
402
  if (!target)
192
403
  throw new Error(`Target "${targetId}" does not exist!`);
193
- // Ensure the config tree exists
194
- // We don't care about setting missing defaults, as this is target-level configuration,
195
- // so any missing properties will fallback to project-level config.
196
- if (!target.yosys)
197
- target.yosys = {};
198
- if (!target.yosys.options)
199
- target.yosys.options = {};
200
- target.yosys.options.topLevelModule = module;
201
- }
202
- setTestbenchPath(targetId, testbenchPath) {
404
+ const cfg = target.config;
405
+ if (!cfg.yosys)
406
+ cfg.yosys = {};
407
+ if (!cfg.yosys.options)
408
+ cfg.yosys.options = {};
409
+ cfg.yosys.options.topLevelModule = module;
410
+ }
411
+ setActiveTestbenchPath(targetId, testbenchPath) {
203
412
  const testbenchFiles = this.getInputFiles()
204
413
  .filter((file) => file.type === 'testbench')
205
414
  .map((file) => file.path);
@@ -208,14 +417,30 @@ class Project {
208
417
  const target = this.getTarget(targetId);
209
418
  if (!target)
210
419
  throw new Error(`Target "${targetId}" does not exist!`);
211
- // Ensure the config tree exists
212
- // We don't care about setting missing defaults, as this is target-level configuration,
213
- // so any missing properties will fallback to project-level config.
214
- if (!target.iverilog)
215
- target.iverilog = {};
216
- if (!target.iverilog.options)
217
- target.iverilog.options = {};
218
- target.iverilog.options.testbenchFile = testbenchPath;
420
+ target.setConfig(['iverilog', 'options', 'testbenchFile'], testbenchPath);
421
+ }
422
+ getActiveTestbenchPath(targetId) {
423
+ const target = this.getTarget(targetId);
424
+ if (!target)
425
+ throw new Error(`Target "${targetId}" does not exist!`);
426
+ return target.getEffectiveOptions('iverilog').testbenchFile;
427
+ }
428
+ setActivePinConfigPath(targetId, pinConfigPath) {
429
+ const pinConfigFiles = this.getInputFiles()
430
+ .filter((file) => file.type === 'pinconfig')
431
+ .map((file) => file.path);
432
+ if (pinConfigPath && !pinConfigFiles.includes(pinConfigPath))
433
+ throw new Error(`Pin config file ${pinConfigPath} is not marked as such!`);
434
+ const target = this.getTarget(targetId);
435
+ if (!target)
436
+ throw new Error(`Target "${targetId}" does not exist!`);
437
+ target.setConfig(['nextpnr', 'options', 'pinConfigFile'], pinConfigPath);
438
+ }
439
+ getActivePinConfigPath(targetId) {
440
+ const target = this.getTarget(targetId);
441
+ if (!target)
442
+ throw new Error(`Target "${targetId}" does not exist!`);
443
+ return target.getEffectiveOptions('nextpnr').pinConfigFile;
219
444
  }
220
445
  setInputFileType(filePath, type) {
221
446
  const file = this.getInputFile(filePath);
@@ -223,11 +448,64 @@ class Project {
223
448
  console.warn(`Tried to set file type of missing input file: ${filePath}`);
224
449
  return;
225
450
  }
226
- file.type = type;
451
+ file.type = type; // internal setter triggers event; batched by decorator
452
+ }
453
+ getTargets() {
454
+ return this.configuration.targets.map(t => new ProjectTarget(this, t));
455
+ }
456
+ hasTarget(id) {
457
+ return this.configuration.targets.some(t => t.id === id);
227
458
  }
228
459
  getTarget(id) {
229
- const targets = this.configuration.targets;
230
- return targets.find((target) => target.id === id) ?? null;
460
+ const t = this.configuration.targets.find(t => t.id === id);
461
+ return t ? new ProjectTarget(this, t) : null;
462
+ }
463
+ getActiveTarget() {
464
+ if (!this.configuration.activeTargetId)
465
+ return null;
466
+ return this.getTarget(this.configuration.activeTargetId);
467
+ }
468
+ setActiveTarget(id) {
469
+ if (id !== null && !this.hasTarget(id)) {
470
+ throw new Error(`Target with ID "${id}" does not exist!`);
471
+ }
472
+ this.configuration.activeTargetId = id ?? undefined;
473
+ }
474
+ addTarget(id, config) {
475
+ if (!id) {
476
+ // Generate a unique ID
477
+ let idx = 1;
478
+ while (this.hasTarget(`target${idx}`))
479
+ idx += 1;
480
+ id = `target${idx}`;
481
+ }
482
+ else if (this.hasTarget(id)) {
483
+ throw new Error(`Target with ID "${id}" already exists!`);
484
+ }
485
+ const newTargetObj = {
486
+ ...safeStructuredClone(config || configuration_js_1.DEFAULT_TARGET),
487
+ id
488
+ };
489
+ this.configuration.targets.push(newTargetObj);
490
+ return new ProjectTarget(this, newTargetObj);
491
+ }
492
+ removeTarget(id) {
493
+ // In-place removal to avoid reassigning the targets array reference
494
+ const targetsArr = this.configuration.targets;
495
+ for (let i = targetsArr.length - 1; i >= 0; i--) {
496
+ if (targetsArr[i].id === id)
497
+ targetsArr.splice(i, 1);
498
+ }
499
+ for (const outFile of this.outputFiles) {
500
+ if (outFile.targetId === id)
501
+ outFile.targetId = null;
502
+ }
503
+ }
504
+ updateTarget(id, updates) {
505
+ const target = this.getTarget(id);
506
+ if (!target)
507
+ throw new Error(`Target with ID "${id}" does not exist!`);
508
+ target.update(updates);
231
509
  }
232
510
  getConfiguration() {
233
511
  return this.configuration;
@@ -237,18 +515,29 @@ class Project {
237
515
  ...this.configuration,
238
516
  ...configuration
239
517
  };
240
- // Unset 'lingering' output file target IDs
518
+ // Remove invalid target references from output files
241
519
  for (const outFile of this.outputFiles) {
242
520
  if (!outFile.target)
243
521
  outFile.targetId = null;
244
522
  }
245
523
  }
246
524
  importFromProject(other, doTriggerEvent = true) {
247
- this.inputFiles = other.getInputFiles().map((file) => file.copy());
525
+ this.name = other.getName();
526
+ this.inputFiles = other.getInputFiles().map((file) => file.copy(this));
248
527
  this.outputFiles = other.getOutputFiles().map((file) => file.copy(this));
249
- this.configuration = structuredClone(other.getConfiguration());
528
+ this.configuration = safeStructuredClone(other.getConfiguration());
250
529
  if (doTriggerEvent)
251
- this.emitEvents('inputFiles', 'outputFiles', 'configuration');
530
+ this.emitEvents('inputFiles', 'outputFiles', 'configuration', 'meta');
531
+ }
532
+ // Public triggers used by file/target objects
533
+ triggerInputFilesChanged() {
534
+ this.emitEvents('inputFiles');
535
+ }
536
+ triggerOutputFilesChanged() {
537
+ this.emitEvents('outputFiles');
538
+ }
539
+ triggerConfigurationChanged() {
540
+ this.emitEvents('configuration');
252
541
  }
253
542
  emitEvents(...events) {
254
543
  for (const event of events)
@@ -259,6 +548,11 @@ class Project {
259
548
  // Do not emit events when batching
260
549
  if (this.batchCounter > 0)
261
550
  return;
551
+ // Make any corrections needed,
552
+ // and add 'configuration' event if any corrections were made
553
+ const didCorrect = this.makeCorrections();
554
+ if (didCorrect)
555
+ this.batchedEvents.add('configuration');
262
556
  // Emit new + batched events
263
557
  if (this.eventCallback)
264
558
  this.eventCallback(this, Array.from(this.batchedEvents));
@@ -271,18 +565,93 @@ class Project {
271
565
  this.emitEvents(...events);
272
566
  return res;
273
567
  }
568
+ ignoreEvents(func) {
569
+ // raise batch counter to ignore events,
570
+ // but don't actually emit them later
571
+ this.batchCounter += 1;
572
+ const res = func();
573
+ this.batchCounter -= 1;
574
+ return res;
575
+ }
274
576
  static emitsEvents(...events) {
275
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
276
577
  return function decorator(_target, _propertyKey, descriptor) {
277
578
  const originalMethod = descriptor.value;
278
- if (!originalMethod)
579
+ if (typeof originalMethod !== 'function')
279
580
  throw new Error('No original method!');
280
581
  descriptor.value = function (...args) {
582
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
281
583
  return this.batchEvents(() => originalMethod.apply(this, args), ...events);
282
584
  };
283
585
  return descriptor;
284
586
  };
285
587
  }
588
+ makeCorrections() {
589
+ return this.ignoreEvents(() => {
590
+ let didChange = false;
591
+ didChange = this.correctTestbenchPath() || didChange;
592
+ didChange = this.correctPinconfigPaths() || didChange;
593
+ didChange = this.correctActiveTarget() || didChange;
594
+ return didChange;
595
+ });
596
+ }
597
+ correctTestbenchPath() {
598
+ const testbenches = this.getInputFiles()
599
+ .filter((file) => file.type == 'testbench')
600
+ .map((file) => file.path);
601
+ let didChange = false;
602
+ for (const target of this.getTargets()) {
603
+ const tbPath = target.getEffectiveOptions('iverilog').testbenchFile;
604
+ if (tbPath && testbenches.includes(tbPath)) {
605
+ // testbench is configured and correct
606
+ continue;
607
+ }
608
+ else if (!tbPath && testbenches.length === 0) {
609
+ // no path configured but also no testbenches present, so ok
610
+ continue;
611
+ }
612
+ const newTb = testbenches.length === 0 ? undefined : testbenches[0];
613
+ this.setActiveTestbenchPath(target.id, newTb);
614
+ didChange = true;
615
+ }
616
+ return didChange;
617
+ }
618
+ correctPinconfigPaths() {
619
+ const pinconfigs = this.getInputFiles()
620
+ .filter((file) => file.type == 'pinconfig')
621
+ .map((file) => file.path);
622
+ let didChange = false;
623
+ for (const target of this.getTargets()) {
624
+ const pcPath = target.getEffectiveOptions('nextpnr').pinConfigFile;
625
+ if (pcPath && pinconfigs.includes(pcPath)) {
626
+ // pinconfig is configured and correct
627
+ continue;
628
+ }
629
+ else if (!pcPath && pinconfigs.length === 0) {
630
+ // no path configured but also no testbenches present, so ok
631
+ continue;
632
+ }
633
+ const newPc = pinconfigs.length === 0 ? undefined : pinconfigs[0];
634
+ this.setActivePinConfigPath(target.id, newPc);
635
+ didChange = true;
636
+ }
637
+ return didChange;
638
+ }
639
+ correctActiveTarget() {
640
+ const activeTarget = this.getActiveTarget();
641
+ if (activeTarget)
642
+ return false; // active target exists, so ok
643
+ const activeTargetId = this.configuration.activeTargetId;
644
+ if (activeTargetId && this.hasTarget(activeTargetId))
645
+ return false; // active target ID is valid, so ok
646
+ // No active target or invalid active target ID, so set to first target (if any)
647
+ if (this.configuration.targets.length > 0) {
648
+ this.configuration.activeTargetId = this.configuration.targets[0].id;
649
+ }
650
+ else {
651
+ this.configuration.activeTargetId = undefined;
652
+ }
653
+ return true;
654
+ }
286
655
  static serialize(project) {
287
656
  return {
288
657
  name: project.name,
@@ -309,6 +678,12 @@ class Project {
309
678
  }
310
679
  }
311
680
  exports.Project = Project;
681
+ __decorate([
682
+ Project.emitsEvents('meta'),
683
+ __metadata("design:type", Function),
684
+ __metadata("design:paramtypes", [String]),
685
+ __metadata("design:returntype", void 0)
686
+ ], Project.prototype, "setName", null);
312
687
  __decorate([
313
688
  Project.emitsEvents('inputFiles'),
314
689
  __metadata("design:type", Function),
@@ -350,13 +725,37 @@ __decorate([
350
725
  __metadata("design:type", Function),
351
726
  __metadata("design:paramtypes", [String, String]),
352
727
  __metadata("design:returntype", void 0)
353
- ], Project.prototype, "setTestbenchPath", null);
728
+ ], Project.prototype, "setActiveTestbenchPath", null);
729
+ __decorate([
730
+ Project.emitsEvents('configuration'),
731
+ __metadata("design:type", Function),
732
+ __metadata("design:paramtypes", [String, String]),
733
+ __metadata("design:returntype", void 0)
734
+ ], Project.prototype, "setActivePinConfigPath", null);
354
735
  __decorate([
355
736
  Project.emitsEvents('inputFiles'),
356
737
  __metadata("design:type", Function),
357
738
  __metadata("design:paramtypes", [String, Object]),
358
739
  __metadata("design:returntype", void 0)
359
740
  ], Project.prototype, "setInputFileType", null);
741
+ __decorate([
742
+ Project.emitsEvents('configuration'),
743
+ __metadata("design:type", Function),
744
+ __metadata("design:paramtypes", [Object]),
745
+ __metadata("design:returntype", void 0)
746
+ ], Project.prototype, "setActiveTarget", null);
747
+ __decorate([
748
+ Project.emitsEvents('configuration'),
749
+ __metadata("design:type", Function),
750
+ __metadata("design:paramtypes", [String, Object]),
751
+ __metadata("design:returntype", ProjectTarget)
752
+ ], Project.prototype, "addTarget", null);
753
+ __decorate([
754
+ Project.emitsEvents('configuration'),
755
+ __metadata("design:type", Function),
756
+ __metadata("design:paramtypes", [String]),
757
+ __metadata("design:returntype", void 0)
758
+ ], Project.prototype, "removeTarget", null);
360
759
  __decorate([
361
760
  Project.emitsEvents('configuration'),
362
761
  __metadata("design:type", Function),