Merge lp:~townsend/libertine/add-extra-archives into lp:libertine
- add-extra-archives
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Stephen M. Webb | ||||
Approved revision: | 180 | ||||
Merged at revision: | 180 | ||||
Proposed branch: | lp:~townsend/libertine/add-extra-archives | ||||
Merge into: | lp:libertine | ||||
Diff against target: |
1149 lines (+692/-76) 17 files modified
libertine/ContainerArchivesList.cpp (+94/-0) libertine/ContainerArchivesList.h (+81/-0) libertine/ContainerConfig.cpp (+123/-17) libertine/ContainerConfig.h (+40/-14) libertine/ContainerConfigList.cpp (+15/-1) libertine/ContainerConfigList.h (+4/-0) libertine/ContainerManager.cpp (+10/-0) libertine/ContainerManager.h (+1/-0) libertine/libertine.cpp (+3/-0) libertine/libertine.h (+2/-0) libertine/qml/ConfigureContainer.qml (+34/-36) libertine/qml/ExtraArchivesView.qml (+167/-0) liblibertine/CMakeLists.txt (+1/-0) python/libertine/ChrootContainer.py (+5/-2) python/libertine/Libertine.py (+15/-4) python/libertine/LxcContainer.py (+2/-0) tools/libertine-container-manager (+95/-2) |
||||
To merge this branch: | bzr merge lp:~townsend/libertine/add-extra-archives | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Stephen M. Webb (community) | Approve | ||
Libertine CI Bot | continuous-integration | Approve | |
Review via email:
|
Commit message
Enable feature to add and remove extra archives in the container. This is for PPA's only with this commit.
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Libertine CI Bot (libertine-ci-bot) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Libertine CI Bot (libertine-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:180
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
None: https:/
Click here to trigger a rebuild:
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Stephen M. Webb (bregma) wrote : | # |
Verified on desktop.
Preview Diff
1 | === added file 'libertine/ContainerArchivesList.cpp' |
2 | --- libertine/ContainerArchivesList.cpp 1970-01-01 00:00:00 +0000 |
3 | +++ libertine/ContainerArchivesList.cpp 2016-03-10 21:34:32 +0000 |
4 | @@ -0,0 +1,94 @@ |
5 | +/** |
6 | + * @file ContainerArchivesList.cpp |
7 | + * @brief Libertine Manager list of extra container archives, ie, PPAs |
8 | + */ |
9 | +/* |
10 | + * Copyright 2016 Canonical Ltd |
11 | + * |
12 | + * Libertine is free software: you can redistribute it and/or modify it under |
13 | + * the terms of the GNU General Public License, version 3, as published by the |
14 | + * Free Software Foundation. |
15 | + * |
16 | + * Libertine is distributed in the hope that it will be useful, but WITHOUT ANY |
17 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
18 | + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
19 | + * |
20 | + * You should have received a copy of the GNU General Public License |
21 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
22 | + */ |
23 | +#include "libertine/ContainerArchivesList.h" |
24 | +#include "libertine/ContainerConfigList.h" |
25 | + |
26 | +ContainerArchivesList:: |
27 | +ContainerArchivesList(ContainerConfigList* container_config_list, |
28 | + QObject* parent) |
29 | +: QAbstractListModel(parent) |
30 | +, container_config_list_(container_config_list) |
31 | +{ } |
32 | + |
33 | + |
34 | +ContainerArchivesList:: |
35 | +~ContainerArchivesList() |
36 | +{ } |
37 | + |
38 | + |
39 | +void ContainerArchivesList:: |
40 | +setContainerArchives(QString const& container_id) |
41 | +{ |
42 | + archives_ = container_config_list_->getArchivesForContainer(container_id); |
43 | + |
44 | + beginResetModel(); |
45 | + endResetModel(); |
46 | +} |
47 | + |
48 | + |
49 | +bool ContainerArchivesList:: |
50 | +empty() const noexcept |
51 | +{ return archives_->empty(); } |
52 | + |
53 | + |
54 | +ContainerArchivesList::size_type ContainerArchivesList:: |
55 | +size() const noexcept |
56 | +{ return archives_->count(); } |
57 | + |
58 | + |
59 | +int ContainerArchivesList:: |
60 | +rowCount(QModelIndex const&) const |
61 | +{ |
62 | + return this->size(); |
63 | +} |
64 | + |
65 | + |
66 | +QHash<int, QByteArray> ContainerArchivesList:: |
67 | +roleNames() const |
68 | +{ |
69 | + QHash<int, QByteArray> roles; |
70 | + roles[static_cast<int>(DataRole::ArchiveName)] = "archiveName"; |
71 | + roles[static_cast<int>(DataRole::ArchiveStatus)] = "archiveStatus"; |
72 | + |
73 | + return roles; |
74 | +} |
75 | + |
76 | + |
77 | +QVariant ContainerArchivesList:: |
78 | +data(QModelIndex const& index, int role) const |
79 | +{ |
80 | + QVariant result; |
81 | + |
82 | + if (index.isValid() && index.row() <= archives_->count()) |
83 | + { |
84 | + switch (static_cast<DataRole>(role)) |
85 | + { |
86 | + case DataRole::ArchiveName: |
87 | + result = (*archives_)[index.row()]->archive_name(); |
88 | + break; |
89 | + case DataRole::ArchiveStatus: |
90 | + result = (*archives_)[index.row()]->archive_status(); |
91 | + break; |
92 | + case DataRole::Error: |
93 | + break; |
94 | + } |
95 | + } |
96 | + |
97 | + return result; |
98 | +} |
99 | |
100 | === added file 'libertine/ContainerArchivesList.h' |
101 | --- libertine/ContainerArchivesList.h 1970-01-01 00:00:00 +0000 |
102 | +++ libertine/ContainerArchivesList.h 2016-03-10 21:34:32 +0000 |
103 | @@ -0,0 +1,81 @@ |
104 | +/** |
105 | + * @file ContainerArchivesList.h |
106 | + * @brief Libertine Manager list of extra container archives (PPAs) |
107 | + */ |
108 | +/* |
109 | + * Copyright 2016 Canonical Ltd |
110 | + * |
111 | + * Libertine is free software: you can redistribute it and/or modify it under |
112 | + * the terms of the GNU General Public License, version 3, as published by the |
113 | + * Free Software Foundation. |
114 | + * |
115 | + * Libertine is distributed in the hope that it will be useful, but WITHOUT ANY |
116 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
117 | + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
118 | + * |
119 | + * You should have received a copy of the GNU General Public License |
120 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
121 | + */ |
122 | +#ifndef _CONTAINER_ARCHIVES_LIST_H_ |
123 | +#define _CONTAINER_ARCHIVES_LIST_H_ |
124 | + |
125 | +#include "libertine/ContainerConfig.h" |
126 | + |
127 | +#include <QtCore/QAbstractListModel> |
128 | +#include <QtCore/QList> |
129 | +#include <QtCore/QObject> |
130 | +#include <QtCore/QString> |
131 | + |
132 | + |
133 | +class ContainerArchives; |
134 | +class ContainerConfigList; |
135 | + |
136 | +class ContainerArchivesList |
137 | +: public QAbstractListModel |
138 | +{ |
139 | + Q_OBJECT |
140 | + |
141 | +public: |
142 | + using ArchivesList = QList<ContainerArchives*>; |
143 | + using iterator = ArchivesList::iterator; |
144 | + using size_type = ArchivesList::size_type; |
145 | + |
146 | + enum class DataRole |
147 | + : int |
148 | + { |
149 | + ArchiveName = Qt::UserRole + 1, |
150 | + ArchiveStatus, |
151 | + Error |
152 | + }; |
153 | + |
154 | +public: |
155 | + explicit |
156 | + ContainerArchivesList(ContainerConfigList* container_config_list, |
157 | + QObject* parent = nullptr); |
158 | + |
159 | + ~ContainerArchivesList(); |
160 | + |
161 | + Q_INVOKABLE void |
162 | + setContainerArchives(QString const& container_id); |
163 | + |
164 | + Q_INVOKABLE bool |
165 | + empty() const noexcept; |
166 | + |
167 | + size_type |
168 | + size() const noexcept; |
169 | + |
170 | + int |
171 | + rowCount(QModelIndex const& parent = QModelIndex()) const; |
172 | + |
173 | + QHash<int, QByteArray> |
174 | + roleNames() const; |
175 | + |
176 | + QVariant |
177 | + data(QModelIndex const& index, int role = Qt::DisplayRole) const; |
178 | + |
179 | +private: |
180 | + ContainerConfigList* container_config_list_; |
181 | + ArchivesList* archives_; |
182 | +}; |
183 | + |
184 | +#endif /* _CONTAINER_ARCHIVES_LIST_H_ */ |
185 | |
186 | === modified file 'libertine/ContainerConfig.cpp' |
187 | --- libertine/ContainerConfig.cpp 2016-02-10 19:31:10 +0000 |
188 | +++ libertine/ContainerConfig.cpp 2016-03-10 21:34:32 +0000 |
189 | @@ -178,15 +178,15 @@ |
190 | return ContainerConfig::InstallStatus::New; |
191 | } |
192 | |
193 | - const static struct { QString string; ContainerApps::AppStatus enumeration; } app_status_names[] = |
194 | + const static struct { QString string; CurrentStatus enumeration; } status_names[] = |
195 | { |
196 | - { QObject::tr("new"), ContainerApps::AppStatus::New }, |
197 | - { QObject::tr("installing"), ContainerApps::AppStatus::Installing }, |
198 | - { QObject::tr("installed"), ContainerApps::AppStatus::Installed }, |
199 | - { QObject::tr("failed"), ContainerApps::AppStatus::Failed }, |
200 | - { QObject::tr("removing"), ContainerApps::AppStatus::Removing }, |
201 | - { QObject::tr("removed"), ContainerApps::AppStatus::Removed }, |
202 | - { QString(), ContainerApps::AppStatus::New } |
203 | + { QObject::tr("new"), CurrentStatus::New }, |
204 | + { QObject::tr("installing"), CurrentStatus::Installing }, |
205 | + { QObject::tr("installed"), CurrentStatus::Installed }, |
206 | + { QObject::tr("failed"), CurrentStatus::Failed }, |
207 | + { QObject::tr("removing"), CurrentStatus::Removing }, |
208 | + { QObject::tr("removed"), CurrentStatus::Removed }, |
209 | + { QString(), CurrentStatus::New } |
210 | }; |
211 | |
212 | QString |
213 | @@ -207,10 +207,10 @@ |
214 | return package_name; |
215 | } |
216 | |
217 | - ContainerApps::AppStatus |
218 | + CurrentStatus |
219 | extract_app_status_from_json(QJsonObject const& json_object) |
220 | { |
221 | - ContainerApps::AppStatus app_status = ContainerApps::AppStatus::New; |
222 | + CurrentStatus app_status = CurrentStatus::New; |
223 | |
224 | QJsonValue value = json_object["appStatus"]; |
225 | if (value != QJsonValue::Undefined) |
226 | @@ -221,7 +221,7 @@ |
227 | QString s = value.toString(); |
228 | if (s.length() > 0) |
229 | { |
230 | - for (auto const& name: app_status_names) |
231 | + for (auto const& name: status_names) |
232 | { |
233 | if (0 == s.compare(name.string, Qt::CaseInsensitive)) |
234 | { |
235 | @@ -241,7 +241,7 @@ |
236 | { |
237 | QList<ContainerApps*> container_apps; |
238 | QString package_name; |
239 | - ContainerApps::AppStatus app_status; |
240 | + CurrentStatus app_status; |
241 | |
242 | QJsonArray installed_apps = json_object["installedApps"].toArray(); |
243 | |
244 | @@ -255,12 +255,78 @@ |
245 | } |
246 | return container_apps; |
247 | } |
248 | + |
249 | + QString |
250 | + extract_archive_name_from_json(QJsonObject const& json_object) |
251 | + { |
252 | + QString archive_name; |
253 | + |
254 | + QJsonValue value = json_object["archiveName"]; |
255 | + if (value != QJsonValue::Undefined) |
256 | + { |
257 | + QJsonValue::Type value_type = value.type(); |
258 | + if (value_type == QJsonValue::String) |
259 | + { |
260 | + archive_name = value.toString(); |
261 | + } |
262 | + } |
263 | + |
264 | + return archive_name; |
265 | + } |
266 | + |
267 | + CurrentStatus |
268 | + extract_archive_status_from_json(QJsonObject const& json_object) |
269 | + { |
270 | + CurrentStatus archive_status = CurrentStatus::New; |
271 | + |
272 | + QJsonValue value = json_object["archiveStatus"]; |
273 | + if (value != QJsonValue::Undefined) |
274 | + { |
275 | + QJsonValue::Type value_type = value.type(); |
276 | + if (value_type == QJsonValue::String) |
277 | + { |
278 | + QString s = value.toString(); |
279 | + if (s.length() > 0) |
280 | + { |
281 | + for (auto const& name: status_names) |
282 | + { |
283 | + if (0 == s.compare(name.string, Qt::CaseInsensitive)) |
284 | + { |
285 | + archive_status = name.enumeration; |
286 | + break; |
287 | + } |
288 | + } |
289 | + } |
290 | + } |
291 | + } |
292 | + |
293 | + return archive_status; |
294 | + } |
295 | + |
296 | + QList<ContainerArchives*> |
297 | + extract_container_archives_from_json(QJsonObject const& json_object) |
298 | + { |
299 | + QList<ContainerArchives*> container_archives; |
300 | + QString archive_name; |
301 | + CurrentStatus archive_status; |
302 | + |
303 | + QJsonArray extra_archives = json_object["extraArchives"].toArray(); |
304 | + |
305 | + for (auto const& archive: extra_archives) |
306 | + { |
307 | + archive_name = extract_archive_name_from_json(archive.toObject()); |
308 | + archive_status = extract_archive_status_from_json(archive.toObject()); |
309 | + |
310 | + container_archives.append(new ContainerArchives(archive_name, archive_status)); |
311 | + } |
312 | + return container_archives; |
313 | + } |
314 | } // anonymous namespace |
315 | |
316 | |
317 | ContainerApps:: |
318 | ContainerApps(QString const& package_name, |
319 | - ContainerApps::AppStatus app_status, |
320 | + CurrentStatus app_status, |
321 | QObject* parent) |
322 | : QObject(parent) |
323 | , package_name_(package_name) |
324 | @@ -280,7 +346,32 @@ |
325 | |
326 | QString const& ContainerApps:: |
327 | app_status() const |
328 | -{ return app_status_names[(int)app_status_].string; } |
329 | +{ return status_names[(int)app_status_].string; } |
330 | + |
331 | + |
332 | +ContainerArchives:: |
333 | +ContainerArchives(QString const& archive_name, |
334 | + CurrentStatus archive_status, |
335 | + QObject* parent) |
336 | +: QObject(parent) |
337 | +, archive_name_(archive_name) |
338 | +, archive_status_(archive_status) |
339 | +{ } |
340 | + |
341 | + |
342 | +ContainerArchives:: |
343 | +~ContainerArchives() |
344 | +{ } |
345 | + |
346 | + |
347 | +QString const& ContainerArchives:: |
348 | +archive_name() const |
349 | +{ return archive_name_; } |
350 | + |
351 | + |
352 | +QString const& ContainerArchives:: |
353 | +archive_status() const |
354 | +{ return status_names[(int)archive_status_].string; } |
355 | |
356 | |
357 | ContainerConfig:: |
358 | @@ -316,6 +407,7 @@ |
359 | , multiarch_support_(extract_multiarch_support_from_json(json_object)) |
360 | , install_status_(extract_install_status_from_json(json_object)) |
361 | , container_apps_(extract_container_apps_from_json(json_object)) |
362 | +, container_archives_(extract_container_archives_from_json(json_object)) |
363 | { } |
364 | |
365 | |
366 | @@ -375,12 +467,19 @@ |
367 | { return container_apps_; } |
368 | |
369 | |
370 | +QList<ContainerArchives*> & ContainerConfig:: |
371 | +container_archives() |
372 | +{ return container_archives_; } |
373 | + |
374 | + |
375 | QJsonObject ContainerConfig:: |
376 | toJson() const |
377 | { |
378 | QJsonObject json_object, |
379 | - app_object; |
380 | - QJsonArray apps; |
381 | + app_object, |
382 | + archive_object; |
383 | + QJsonArray apps, |
384 | + archives; |
385 | |
386 | json_object["id"] = container_id_; |
387 | json_object["name"] = container_name_; |
388 | @@ -397,10 +496,17 @@ |
389 | for (auto const& container_app: container_apps_) |
390 | { |
391 | app_object["packageName"] = container_app->package_name(); |
392 | - app_object["appStatus"] = app_status_names[0].string; |
393 | + app_object["appStatus"] = status_names[0].string; |
394 | apps.append(app_object); |
395 | } |
396 | json_object["installedApps"] = apps; |
397 | |
398 | + for (auto const& container_archive: container_archives_) |
399 | + { |
400 | + archive_object["archiveName"] = container_archive->archive_name(); |
401 | + archives.append(archive_object); |
402 | + } |
403 | + json_object["extraArchives"] = archives; |
404 | + |
405 | return json_object; |
406 | } |
407 | |
408 | === modified file 'libertine/ContainerConfig.h' |
409 | --- libertine/ContainerConfig.h 2016-02-10 19:31:10 +0000 |
410 | +++ libertine/ContainerConfig.h 2016-03-10 21:34:32 +0000 |
411 | @@ -24,18 +24,17 @@ |
412 | #include <QtCore/QString> |
413 | |
414 | |
415 | +enum class CurrentStatus { New, Installing, Installed, Failed, Removing, Removed }; |
416 | + |
417 | + |
418 | class ContainerApps |
419 | : public QObject |
420 | { |
421 | Q_OBJECT |
422 | |
423 | public: |
424 | - enum class AppStatus |
425 | - { New, Installing, Installed, Failed, Removing, Removed }; |
426 | - |
427 | -public: |
428 | ContainerApps(QString const& package_name, |
429 | - AppStatus app_status, |
430 | + CurrentStatus app_status, |
431 | QObject* parent = nullptr); |
432 | ~ContainerApps(); |
433 | |
434 | @@ -46,8 +45,31 @@ |
435 | app_status() const; |
436 | |
437 | private: |
438 | - QString package_name_; |
439 | - AppStatus app_status_; |
440 | + QString package_name_; |
441 | + CurrentStatus app_status_; |
442 | +}; |
443 | + |
444 | + |
445 | +class ContainerArchives |
446 | +: public QObject |
447 | +{ |
448 | + Q_OBJECT |
449 | + |
450 | +public: |
451 | + ContainerArchives(QString const& archive_name, |
452 | + CurrentStatus archive_status, |
453 | + QObject* parent = nullptr); |
454 | + ~ContainerArchives(); |
455 | + |
456 | + QString const& |
457 | + archive_name() const; |
458 | + |
459 | + QString const& |
460 | + archive_status() const; |
461 | + |
462 | +private: |
463 | + QString archive_name_; |
464 | + CurrentStatus archive_status_; |
465 | }; |
466 | |
467 | |
468 | @@ -107,6 +129,9 @@ |
469 | QList<ContainerApps*> & |
470 | container_apps(); |
471 | |
472 | + QList<ContainerArchives*> & |
473 | + container_archives(); |
474 | + |
475 | QJsonObject |
476 | toJson() const; |
477 | |
478 | @@ -115,13 +140,14 @@ |
479 | void installStatusChanged(); |
480 | |
481 | private: |
482 | - QString container_id_; |
483 | - QString container_name_; |
484 | - QString container_type_; |
485 | - QString distro_series_; |
486 | - QString multiarch_support_; |
487 | - InstallStatus install_status_; |
488 | - QList<ContainerApps*> container_apps_; |
489 | + QString container_id_; |
490 | + QString container_name_; |
491 | + QString container_type_; |
492 | + QString distro_series_; |
493 | + QString multiarch_support_; |
494 | + InstallStatus install_status_; |
495 | + QList<ContainerApps*> container_apps_; |
496 | + QList<ContainerArchives*> container_archives_; |
497 | }; |
498 | |
499 | #endif /* CONTAINER_CONTAINERCONFIG_H */ |
500 | |
501 | === modified file 'libertine/ContainerConfigList.cpp' |
502 | --- libertine/ContainerConfigList.cpp 2016-02-11 15:25:09 +0000 |
503 | +++ libertine/ContainerConfigList.cpp 2016-03-10 21:34:32 +0000 |
504 | @@ -121,7 +121,7 @@ |
505 | { |
506 | if (config->container_id() == container_id) |
507 | { |
508 | - config->container_apps().append(new ContainerApps(package_name, ContainerApps::AppStatus::New, this)); |
509 | + config->container_apps().append(new ContainerApps(package_name, CurrentStatus::New, this)); |
510 | break; |
511 | } |
512 | } |
513 | @@ -193,6 +193,20 @@ |
514 | } |
515 | |
516 | |
517 | +QList<ContainerArchives*> * ContainerConfigList:: |
518 | +getArchivesForContainer(QString const& container_id) |
519 | +{ |
520 | + for (auto const& config: configs_) |
521 | + { |
522 | + if (config->container_id() == container_id) |
523 | + { |
524 | + return &(config->container_archives()); |
525 | + } |
526 | + } |
527 | + return nullptr; |
528 | +} |
529 | + |
530 | + |
531 | QString ContainerConfigList:: |
532 | getContainerType(QString const& container_id) |
533 | { |
534 | |
535 | === modified file 'libertine/ContainerConfigList.h' |
536 | --- libertine/ContainerConfigList.h 2016-02-11 15:25:09 +0000 |
537 | +++ libertine/ContainerConfigList.h 2016-03-10 21:34:32 +0000 |
538 | @@ -25,6 +25,7 @@ |
539 | |
540 | |
541 | class ContainerApps; |
542 | +class ContainerArchives; |
543 | class ContainerConfig; |
544 | class LibertineConfig; |
545 | |
546 | @@ -112,6 +113,9 @@ |
547 | Q_INVOKABLE QString |
548 | getAppVersion(QString const& app_info); |
549 | |
550 | + QList<ContainerArchives*> * |
551 | + getArchivesForContainer(QString const& container_id); |
552 | + |
553 | Q_INVOKABLE QString |
554 | getContainerType(QString const& container_id); |
555 | |
556 | |
557 | === modified file 'libertine/ContainerManager.cpp' |
558 | --- libertine/ContainerManager.cpp 2016-02-11 15:59:00 +0000 |
559 | +++ libertine/ContainerManager.cpp 2016-03-10 21:34:32 +0000 |
560 | @@ -395,6 +395,9 @@ |
561 | void ContainerManagerWorker:: |
562 | configureContainer(QStringList configure_command) |
563 | { |
564 | + QByteArray error_msg; |
565 | + bool result = true; |
566 | + |
567 | QProcess libertine_cli_tool; |
568 | QString exec_line = libertine_container_manager_tool; |
569 | QStringList args; |
570 | @@ -408,6 +411,13 @@ |
571 | |
572 | libertine_cli_tool.waitForFinished(-1); |
573 | |
574 | + if (libertine_cli_tool.exitCode() != 0) |
575 | + { |
576 | + error_msg = libertine_cli_tool.readAllStandardOutput(); |
577 | + result = false; |
578 | + } |
579 | + |
580 | + emit finishedConfigure(result, QString(error_msg)); |
581 | emit finished(); |
582 | quit(); |
583 | } |
584 | |
585 | === modified file 'libertine/ContainerManager.h' |
586 | --- libertine/ContainerManager.h 2016-02-11 16:25:49 +0000 |
587 | +++ libertine/ContainerManager.h 2016-03-10 21:34:32 +0000 |
588 | @@ -146,6 +146,7 @@ |
589 | void finishedRemove(bool result, QString const& error_msg); |
590 | void finishedSearch(bool result, QList<QString> packageList); |
591 | void finishedCommand(QString const& command_output); |
592 | + void finishedConfigure(bool result, QString const& error_msg); |
593 | }; |
594 | |
595 | #endif /* CONTAINER_CONTAINERMANAGER_H_ */ |
596 | |
597 | === modified file 'libertine/libertine.cpp' |
598 | --- libertine/libertine.cpp 2016-01-20 17:34:19 +0000 |
599 | +++ libertine/libertine.cpp 2016-03-10 21:34:32 +0000 |
600 | @@ -21,6 +21,7 @@ |
601 | #include <cstdlib> |
602 | #include "libertine/ContainerManager.h" |
603 | #include "libertine/ContainerAppsList.h" |
604 | +#include "libertine/ContainerArchivesList.h" |
605 | #include "libertine/ContainerConfig.h" |
606 | #include "libertine/ContainerConfigList.h" |
607 | #include "libertine/libertine.h" |
608 | @@ -105,6 +106,7 @@ |
609 | |
610 | containers_ = new ContainerConfigList(config_.data(), this); |
611 | container_apps_ = new ContainerAppsList(containers_, this); |
612 | + container_archives_ = new ContainerArchivesList(containers_, this); |
613 | password_helper_ = new PasswordHelper(); |
614 | |
615 | initialize_view(); |
616 | @@ -131,6 +133,7 @@ |
617 | QQmlContext* ctxt = view_.rootContext(); |
618 | ctxt->setContextProperty("containerConfigList", containers_); |
619 | ctxt->setContextProperty("containerAppsList", container_apps_); |
620 | + ctxt->setContextProperty("containerArchivesList", container_archives_); |
621 | ctxt->setContextProperty("passwordHelper", password_helper_); |
622 | |
623 | view_.setSource(QUrl::fromLocalFile(main_qml_source_file_)); |
624 | |
625 | === modified file 'libertine/libertine.h' |
626 | --- libertine/libertine.h 2016-01-20 17:34:19 +0000 |
627 | +++ libertine/libertine.h 2016-03-10 21:34:32 +0000 |
628 | @@ -30,6 +30,7 @@ |
629 | class LibertineConfig; |
630 | class PasswordHelper; |
631 | class ContainerAppsList; |
632 | +class ContainerArchivesList; |
633 | |
634 | |
635 | class Libertine |
636 | @@ -55,6 +56,7 @@ |
637 | QFileSystemWatcher watcher_; |
638 | ContainerConfigList* containers_; |
639 | ContainerAppsList* container_apps_; |
640 | + ContainerArchivesList* container_archives_; |
641 | PasswordHelper* password_helper_; |
642 | QQuickView view_; |
643 | }; |
644 | |
645 | === modified file 'libertine/qml/ConfigureContainer.qml' |
646 | --- libertine/qml/ConfigureContainer.qml 2016-02-11 15:25:09 +0000 |
647 | +++ libertine/qml/ConfigureContainer.qml 2016-03-10 21:34:32 +0000 |
648 | @@ -1,5 +1,5 @@ |
649 | /** |
650 | - * @file configureContainer.qml |
651 | + * @file ConfigureContainer.qml |
652 | * @brief Libertine configure container view |
653 | */ |
654 | /* |
655 | @@ -19,51 +19,49 @@ |
656 | import Libertine 1.0 |
657 | import QtQuick 2.4 |
658 | import Ubuntu.Components 1.2 |
659 | +import Ubuntu.Components.ListItems 1.2 as ListItem |
660 | |
661 | |
662 | Page { |
663 | id: configureView |
664 | title: i18n.tr("Configure ") + mainView.currentContainer |
665 | |
666 | - Item { |
667 | - visible: containerConfigList.getHostArchitecture() == 'x86_64' ? true : false |
668 | - CheckBox { |
669 | - id: multiarchCheckBox |
670 | - anchors { |
671 | - left: parent.left |
672 | - top: parent.top |
673 | - leftMargin: configureView.leftMargin |
674 | - } |
675 | - checked: containerConfigList.getContainerMultiarchSupport(mainView.currentContainer) == 'enabled' ? true : false |
676 | - onClicked: { |
677 | - var comp = Qt.createComponent("ContainerManager.qml") |
678 | - if (multiarchCheckBox.checked) { |
679 | - var worker = comp.createObject(mainView, {"containerAction": ContainerManagerWorker.Configure, |
680 | - "containerId": mainView.currentContainer, |
681 | - "containerType": containerConfigList.getContainerType(mainView.currentContainer), |
682 | - "data_list": ["--multiarch", "enable"]}) |
683 | - worker.start() |
684 | + Column { |
685 | + anchors.left: parent.left |
686 | + anchors.right: parent.right |
687 | + |
688 | + ListItem.Standard { |
689 | + visible: containerConfigList.getHostArchitecture() == 'x86_64' ? true : false |
690 | + control: CheckBox { |
691 | + checked: containerConfigList.getContainerMultiarchSupport(mainView.currentContainer) == 'enabled' ? true : false |
692 | + onClicked: { |
693 | + var comp = Qt.createComponent("ContainerManager.qml") |
694 | + if (multiarchCheckBox.checked) { |
695 | + var worker = comp.createObject(mainView, {"containerAction": ContainerManagerWorker.Configure, |
696 | + "containerId": mainView.currentContainer, |
697 | + "containerType": containerConfigList.getContainerType(mainView.currentContainer), |
698 | + "data_list": ["--multiarch", "enable"]}) |
699 | + worker.start() |
700 | + } |
701 | + else { |
702 | + var worker = comp.createObject(mainView, {"containerAction": ContainerManagerWorker.Configure, |
703 | + "containerId": mainView.currentContainer, |
704 | + "containerType": containerConfigList.getContainerType(mainView.currentContainer), |
705 | + "data_list": ["--multiarch", "disable"]}) |
706 | + worker.start() |
707 | + } |
708 | } |
709 | - else { |
710 | - var worker = comp.createObject(mainView, {"containerAction": ContainerManagerWorker.Configure, |
711 | - "containerId": mainView.currentContainer, |
712 | - "containerType": containerConfigList.getContainerType(mainView.currentContainer), |
713 | - "data_list": ["--multiarch", "disable"]}) |
714 | - worker.start() |
715 | - } |
716 | - } |
717 | - } |
718 | - Label { |
719 | - anchors { |
720 | - left: multiarchCheckBox.right |
721 | - right: parent.right |
722 | - top: parent.bottom |
723 | - verticalCenter: parent.verticalCenter |
724 | - leftMargin: units.gu(2) |
725 | - rightMargin: configureView.rightMargin |
726 | } |
727 | text: i18n.tr("i386 multiarch support") |
728 | } |
729 | |
730 | + ListItem.SingleValue { |
731 | + text: i18n.tr("Additional archives and PPAs") |
732 | + progression: true |
733 | + onClicked: { |
734 | + containerArchivesList.setContainerArchives(mainView.currentContainer) |
735 | + pageStack.push(Qt.resolvedUrl("ExtraArchivesView.qml")) |
736 | + } |
737 | + } |
738 | } |
739 | } |
740 | |
741 | === added file 'libertine/qml/ExtraArchivesView.qml' |
742 | --- libertine/qml/ExtraArchivesView.qml 1970-01-01 00:00:00 +0000 |
743 | +++ libertine/qml/ExtraArchivesView.qml 2016-03-10 21:34:32 +0000 |
744 | @@ -0,0 +1,167 @@ |
745 | +/** |
746 | + * @file ExtraArchiveView.qml |
747 | + * @brief Libertine container add archive view |
748 | + */ |
749 | +/* |
750 | + * Copyright 2016 Canonical Ltd |
751 | + * |
752 | + * Libertine is free software: you can redistribute it and/or modify it under |
753 | + * the terms of the GNU General Public License, version 3, as published by the |
754 | + * Free Software Foundation. |
755 | + * |
756 | + * Libertine is distributed in the hope that it will be useful, but WITHOUT ANY |
757 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
758 | + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
759 | + * |
760 | + * You should have received a copy of the GNU General Public License |
761 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
762 | + */ |
763 | +import Libertine 1.0 |
764 | +import QtQuick 2.4 |
765 | +import Ubuntu.Components 1.2 |
766 | +import Ubuntu.Components.Popups 1.2 |
767 | + |
768 | +Page { |
769 | + id: extraArchiveView |
770 | + title: i18n.tr("Additional Archives and PPAs") |
771 | + property var archive_name: null |
772 | + property var worker: null |
773 | + |
774 | + head.actions: [ |
775 | + Action { |
776 | + iconName: "add" |
777 | + text: i18n.tr("add") |
778 | + description: i18n.tr("Add a new PPA") |
779 | + onTriggered: PopupUtils.open(addArchivePopup) |
780 | + } |
781 | + ] |
782 | + |
783 | + Component { |
784 | + id: addArchivePopup |
785 | + Dialog { |
786 | + id: addArchiveDialog |
787 | + title: i18n.tr("Add additional PPA") |
788 | + text: i18n.tr("Enter name of PPA in the form ppa:user/ppa-name:") |
789 | + |
790 | + TextField { |
791 | + id: extraArchiveString |
792 | + onAccepted: { |
793 | + PopupUtils.close(addArchiveDialog) |
794 | + addArchive(text) |
795 | + } |
796 | + } |
797 | + Button { |
798 | + text: i18n.tr("OK") |
799 | + color: UbuntuColors.green |
800 | + onClicked: { |
801 | + PopupUtils.close(addArchiveDialog) |
802 | + addArchive(extraArchiveString.text) |
803 | + } |
804 | + } |
805 | + Button { |
806 | + text: i18n.tr("Cancel") |
807 | + color: UbuntuColors.red |
808 | + onClicked: PopupUtils.close(addArchiveDialog) |
809 | + } |
810 | + Component.onCompleted: { |
811 | + extraArchiveString.forceActiveFocus() |
812 | + } |
813 | + } |
814 | + } |
815 | + |
816 | + UbuntuListView { |
817 | + id: extraArchiveList |
818 | + anchors.fill: parent |
819 | + model: containerArchivesList |
820 | + delegate: ListItem { |
821 | + ActivityIndicator { |
822 | + id: extraArchiveActivity |
823 | + anchors.verticalCenter: parent.verticalCenter |
824 | + visible: (archiveStatus === i18n.tr("installing") || |
825 | + archiveStatus === i18n.tr("removing")) ? true : false |
826 | + running: extraArchiveActivity.visible |
827 | + } |
828 | + Label { |
829 | + anchors { |
830 | + verticalCenter: parent.verticalCenter |
831 | + left: extraArchiveActivity.running ? extraArchiveActivity.right : parent.left |
832 | + leftMargin: units.gu(2) |
833 | + } |
834 | + text: archiveName |
835 | + } |
836 | + leadingActions: ListItemActions { |
837 | + actions: [ |
838 | + Action { |
839 | + iconName: "delete" |
840 | + text: i18n.tr("remove") |
841 | + description: i18n.tr("Remove extra archive") |
842 | + onTriggered: { |
843 | + deleteArchive(archiveName) |
844 | + } |
845 | + } |
846 | + ] |
847 | + } |
848 | + } |
849 | + } |
850 | + |
851 | + function addArchive(archive) { |
852 | + var comp = Qt.createComponent("ContainerManager.qml") |
853 | + worker = comp.createObject(mainView, {"containerAction": ContainerManagerWorker.Configure, |
854 | + "containerId": mainView.currentContainer, |
855 | + "containerType": containerConfigList.getContainerType(mainView.currentContainer), |
856 | + "data_list": ["--add-archive", archive]}) |
857 | + worker.finishedConfigure.connect(finishedConfigure) |
858 | + worker.start() |
859 | + } |
860 | + |
861 | + function deleteArchive(archive) { |
862 | + var comp = Qt.createComponent("ContainerManager.qml") |
863 | + worker = comp.createObject(mainView, {"containerAction": ContainerManagerWorker.Configure, |
864 | + "containerId": mainView.currentContainer, |
865 | + "containerType": containerConfigList.getContainerType(mainView.currentContainer), |
866 | + "data_list": ["--delete-archive", archive]}) |
867 | + worker.finishedConfigure.connect(finishedConfigure) |
868 | + worker.start() |
869 | + } |
870 | + |
871 | + Component { |
872 | + id: addFailedPopup |
873 | + |
874 | + Dialog { |
875 | + property var error_msg: null |
876 | + id: addFailedDialog |
877 | + title: i18n.tr("Adding archive failed") |
878 | + text: error_msg |
879 | + |
880 | + Button { |
881 | + text: i18n.tr("Dismiss") |
882 | + onClicked: PopupUtils.close(addFailedDialog) |
883 | + } |
884 | + } |
885 | + } |
886 | + |
887 | + Component.onCompleted: { |
888 | + containerConfigList.configChanged.connect(reloadArchives) |
889 | + } |
890 | + |
891 | + Component.onDestruction: { |
892 | + containerConfigList.configChanged.disconnect(reloadArchives) |
893 | + |
894 | + if (worker) { |
895 | + worker.finishedConfigure.disconnect(finishedConfigure) |
896 | + } |
897 | + } |
898 | + |
899 | + function reloadArchives() { |
900 | + containerArchivesList.setContainerArchives(mainView.currentContainer) |
901 | + } |
902 | + |
903 | + function finishedConfigure(result, error_msg) { |
904 | + if (result) { |
905 | + containerArchivesList.setContainerArchives(mainView.currentContainer) |
906 | + } |
907 | + else { |
908 | + PopupUtils.open(addFailedPopup, null, {'error_msg': error_msg}) |
909 | + } |
910 | + } |
911 | +} |
912 | |
913 | === modified file 'liblibertine/CMakeLists.txt' |
914 | --- liblibertine/CMakeLists.txt 2015-10-07 18:59:50 +0000 |
915 | +++ liblibertine/CMakeLists.txt 2016-03-10 21:34:32 +0000 |
916 | @@ -11,6 +11,7 @@ |
917 | ${libertine_src}/ContainerConfig.cpp |
918 | ${libertine_src}/ContainerManager.cpp |
919 | ${libertine_src}/ContainerAppsList.cpp |
920 | + ${libertine_src}/ContainerArchivesList.cpp |
921 | ${libertine_src}/PasswordHelper.cpp |
922 | ) |
923 | set_target_properties(libertine-common PROPERTIES |
924 | |
925 | === modified file 'python/libertine/ChrootContainer.py' |
926 | --- python/libertine/ChrootContainer.py 2016-02-08 16:47:18 +0000 |
927 | +++ python/libertine/ChrootContainer.py 2016-03-10 21:34:32 +0000 |
928 | @@ -96,7 +96,7 @@ |
929 | fd.write("done\n") |
930 | os.fchmod(fd.fileno(), 0o755) |
931 | |
932 | - # Add universe and -updates to the chroot's sources.list |
933 | + # Add universe, multiverse, and -updates to the chroot's sources.list |
934 | if (utils.get_host_architecture() == 'armhf'): |
935 | archive = "deb http://ports.ubuntu.com/ubuntu-ports " |
936 | else: |
937 | @@ -105,9 +105,11 @@ |
938 | if verbosity == 1: |
939 | print("Updating chroot's sources.list entries...") |
940 | with open(os.path.join(self.root_path, 'etc', 'apt', 'sources.list'), 'a') as fd: |
941 | + fd.write(archive + installed_release + "-updates main\n") |
942 | fd.write(archive + installed_release + " universe\n") |
943 | - fd.write(archive + installed_release + "-updates main\n") |
944 | fd.write(archive + installed_release + "-updates universe\n") |
945 | + fd.write(archive + installed_release + " multiverse\n") |
946 | + fd.write(archive + installed_release + "-updates multiverse\n") |
947 | |
948 | utils.create_libertine_user_data_dir(self.container_id) |
949 | |
950 | @@ -155,6 +157,7 @@ |
951 | print("Updating the contents of the container after creation...") |
952 | self.update_packages(verbosity) |
953 | self.install_package("libnss-extrausers", verbosity) |
954 | + self.install_package("software-properties-common", verbosity) |
955 | |
956 | if verbosity == 1: |
957 | print("Installing Matchbox as the Xmir window manager...") |
958 | |
959 | === modified file 'python/libertine/Libertine.py' |
960 | --- python/libertine/Libertine.py 2016-02-10 19:31:10 +0000 |
961 | +++ python/libertine/Libertine.py 2016-03-10 21:34:32 +0000 |
962 | @@ -18,6 +18,7 @@ |
963 | import contextlib |
964 | import json |
965 | import libertine.utils |
966 | +import os |
967 | |
968 | |
969 | def get_container_type(container_id): |
970 | @@ -133,10 +134,20 @@ |
971 | """ |
972 | if command == 'multiarch': |
973 | if args[0] == 'enable': |
974 | - self.run_in_container("dpkg --add-architecture i386") |
975 | + return self.run_in_container("dpkg --add-architecture i386") |
976 | else: |
977 | self.run_in_container(apt_command_prefix(verbosity) + "purge \".*:i386\"") |
978 | - self.run_in_container("dpkg --remove-architecture i386") |
979 | + return self.run_in_container("dpkg --remove-architecture i386") |
980 | + |
981 | + elif command == 'add-archive': |
982 | + if not os.path.exists(os.path.join(self.root_path, 'usr', 'bin', 'add-apt-repository')): |
983 | + self.update_packages(verbosity) |
984 | + self.install_package("software-properties-common", verbosity) |
985 | + |
986 | + return self.run_in_container("add-apt-repository -y " + args[0]) |
987 | + |
988 | + elif command == 'delete-archive': |
989 | + return self.run_in_container("add-apt-repository -y -r " + args[0]) |
990 | |
991 | def get_container_distro(self, container_id): |
992 | """ |
993 | @@ -323,8 +334,8 @@ |
994 | :rtype: The output of the given command. |
995 | """ |
996 | with ContainerRunning(self.container): |
997 | - self.container.run_in_container(exec_line) |
998 | + return self.container.run_in_container(exec_line) |
999 | |
1000 | def configure_command(self, command, *args): |
1001 | with ContainerRunning(self.container): |
1002 | - self.container.configure_command(command, *args) |
1003 | + return self.container.configure_command(command, *args) |
1004 | |
1005 | === modified file 'python/libertine/LxcContainer.py' |
1006 | --- python/libertine/LxcContainer.py 2016-02-09 20:59:51 +0000 |
1007 | +++ python/libertine/LxcContainer.py 2016-03-10 21:34:32 +0000 |
1008 | @@ -189,6 +189,8 @@ |
1009 | print("Updating the contents of the container after creation...") |
1010 | self.update_packages(verbosity) |
1011 | |
1012 | + self.install_package("software-properties-common", verbosity) |
1013 | + |
1014 | if verbosity == 1: |
1015 | print("Installing Matchbox as the Xmir window manager...") |
1016 | self.install_package('matchbox', verbosity=verbosity) |
1017 | |
1018 | === modified file 'tools/libertine-container-manager' |
1019 | --- tools/libertine-container-manager 2016-02-10 19:31:10 +0000 |
1020 | +++ tools/libertine-container-manager 2016-03-10 21:34:32 +0000 |
1021 | @@ -127,6 +127,64 @@ |
1022 | return container['multiarch'] |
1023 | |
1024 | |
1025 | +def update_archive_install_status(container_id, archive_name, new_status): |
1026 | + container_list = read_container_config_file() |
1027 | + |
1028 | + for container in container_list['containerList']: |
1029 | + if container['id'] == container_id: |
1030 | + for archive in container['extraArchives']: |
1031 | + if archive['archiveName'] == archive_name: |
1032 | + archive['archiveStatus'] = new_status |
1033 | + write_container_config_file(container_list) |
1034 | + return |
1035 | + |
1036 | + |
1037 | +def add_container_archive(container_id, archive_name): |
1038 | + container_list = read_container_config_file() |
1039 | + |
1040 | + for container in container_list['containerList']: |
1041 | + if container['id'] == container_id: |
1042 | + archive_obj = {'archiveName': archive_name, 'archiveStatus': 'new'} |
1043 | + |
1044 | + if 'extraArchives' not in container: |
1045 | + container['extraArchives'] = [archive_obj] |
1046 | + else: |
1047 | + container['extraArchives'].append(archive_obj) |
1048 | + |
1049 | + write_container_config_file(container_list) |
1050 | + break |
1051 | + |
1052 | + |
1053 | +def delete_container_archive(container_id, archive_name): |
1054 | + container_list = read_container_config_file() |
1055 | + |
1056 | + for container in container_list['containerList']: |
1057 | + if container['id'] == container_id: |
1058 | + for archive in container['extraArchives']: |
1059 | + if archive['archiveName'] == archive_name: |
1060 | + container['extraArchives'].remove(archive) |
1061 | + write_container_config_file(container_list) |
1062 | + return |
1063 | + |
1064 | + print("%s does not exist." % archive_name) |
1065 | + sys.exit(1) |
1066 | + |
1067 | + |
1068 | +def archive_exists(container_id, archive_name): |
1069 | + container_list = read_container_config_file() |
1070 | + |
1071 | + for container in container_list['containerList']: |
1072 | + if container['id'] == container_id: |
1073 | + if 'extraArchives' not in container: |
1074 | + return False |
1075 | + else: |
1076 | + for archive in container['extraArchives']: |
1077 | + if archive['archiveName'] == archive_name: |
1078 | + return True |
1079 | + |
1080 | + return False |
1081 | + |
1082 | + |
1083 | def add_new_container(id, name, type, distro): |
1084 | if not os.path.exists(libertine.utils.get_libertine_database_dir_path()): |
1085 | os.makedirs(libertine.utils.get_libertine_database_dir_path()) |
1086 | @@ -379,7 +437,7 @@ |
1087 | |
1088 | container = LibertineContainer(args.id) |
1089 | |
1090 | - container.exec_command(args.command) |
1091 | + sys.exit(container.exec_command(args.command)) |
1092 | |
1093 | |
1094 | def configure(args): |
1095 | @@ -399,11 +457,36 @@ |
1096 | current_multiarch = get_container_multiarch_support(args.id) |
1097 | if current_multiarch == multiarch: |
1098 | print("i386 multiarch support is already %s" % multiarch) |
1099 | - sys.exit(0) |
1100 | + sys.exit(1) |
1101 | |
1102 | container.configure_command('multiarch', args.multiarch) |
1103 | update_container_multiarch_support(args.id, multiarch) |
1104 | |
1105 | + elif args.add_archive: |
1106 | + if archive_exists(args.id, args.add_archive): |
1107 | + print("%s already added in container." % args.add_archive) |
1108 | + sys.exit(1) |
1109 | + |
1110 | + add_container_archive(args.id, args.add_archive) |
1111 | + update_archive_install_status(args.id, args.add_archive, 'installing') |
1112 | + if container.configure_command('add-archive', args.add_archive) != 0: |
1113 | + delete_container_archive(args.id, args.add_archive) |
1114 | + sys.exit(1) |
1115 | + |
1116 | + update_archive_install_status(args.id, args.add_archive, 'installed') |
1117 | + |
1118 | + |
1119 | + elif args.delete_archive: |
1120 | + if not archive_exists(args.id, args.delete_archive): |
1121 | + print("%s is not added in container." % args.delete_archive) |
1122 | + sys.exit(1) |
1123 | + |
1124 | + update_archive_install_status(args.id, args.delete_archive, 'removing') |
1125 | + if container.configure_command('delete-archive', args.delete_archive) != 0: |
1126 | + sys.exit(1) |
1127 | + |
1128 | + delete_container_archive(args.id, args.delete_archive) |
1129 | + |
1130 | |
1131 | if __name__ == '__main__': |
1132 | parser = argparse.ArgumentParser(description="Legacy X application support for Unity 8") |
1133 | @@ -543,6 +626,16 @@ |
1134 | help=("Enables or disables i386 multiarch support for amd64 Libertine " |
1135 | "containers. This option has no effect when the Libertine " |
1136 | "container is i386.")) |
1137 | + parser_configure.add_argument( |
1138 | + '-a', '--add-archive', |
1139 | + metavar='Archive name', |
1140 | + help=("Adds an archive (PPA) in the specified Libertine container. Needs to be " |
1141 | + "in the form of \"ppa:user/ppa-name\".")) |
1142 | + parser_configure.add_argument( |
1143 | + '-d', '--delete-archive', |
1144 | + metavar='Archive name', |
1145 | + help=("Deletes an existing archive (PPA) in the specified Libertine container. " |
1146 | + "Needs to be in the form of \"ppa:user/ppa-name\".")) |
1147 | parser_configure.set_defaults(func=configure) |
1148 | |
1149 | # Actually parse the args |
PASSED: Continuous integration, rev:179 /jenkins. canonical. com/libertine/ job/libertine- ci-verify- mp/130/ /jenkins. canonical. com/libertine/ job/generic- ci-generate- dsc/133 /jenkins. canonical. com/libertine/ job/libertine- ci-build- and-test- dsc/architectur e=amd64, distribution= vivid,label_ exp=jenkins- slave-0% 20%7C%7C% 20jenkins- slave-2/ 92 /jenkins. canonical. com/libertine/ job/libertine- ci-build- and-test- dsc/architectur e=amd64, distribution= xenial, label_exp= jenkins- slave-0% 20%7C%7C% 20jenkins- slave-2/ 92 /jenkins. canonical. com/libertine/ job/libertine- ci-build- and-test- dsc/architectur e=i386, distribution= vivid,label_ exp=jenkins- slave-0% 20%7C%7C% 20jenkins- slave-2/ 92 /jenkins. canonical. com/libertine/ job/libertine- ci-build- and-test- dsc/architectur e=i386, distribution= xenial, label_exp= jenkins- slave-0% 20%7C%7C% 20jenkins- slave-2/ 92 /jenkins. canonical. com/libertine/ job/libertine- ci-update- mp/1028/ console
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
None: https:/
Click here to trigger a rebuild: /jenkins. canonical. com/libertine/ job/libertine- ci-verify- mp/130/ rebuild
https:/