Skip to content

Commit 411d7d1

Browse files
authored
Support applications installing more than 1 binary (#342)
1 parent e347a6d commit 411d7d1

File tree

5 files changed

+146
-76
lines changed

5 files changed

+146
-76
lines changed

.release-notes/multiple-binary.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## Support applications installing more than 1 binary
2+
3+
The original design of ponyup was that you would install an application such as ponyc that in turn contained a single binary that shared the same name.
4+
5+
We recently started including the Pony language server in the distribution with ponyc. Ponyup could install the distribution but would only install into the user's PATH the ponyc binary.
6+
7+
We've added support for application distributions to contain more than one binary that will be linked into the user's PATH.
8+
9+
The practical implication is that when you install a ponyc distribution that also contains `pony-lsp`, both `ponyc` and `pony-lsp` will be available via your PATH.

cmd/main.pony

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ actor Main is PonyupNotify
9898
let pkg =
9999
try
100100
Packages.from_fragments(
101-
command.arg("package").string(),
101+
Packages.application_from_string(command.arg("package").string())?,
102102
chan(0)?,
103103
try chan(1)? else "latest" end,
104104
platform.string().split("-"))?
@@ -118,7 +118,7 @@ actor Main is PonyupNotify
118118
let pkg =
119119
try
120120
Packages.from_fragments(
121-
command.arg("package").string(),
121+
Packages.application_from_string(command.arg("package").string())?,
122122
chan(0)?,
123123
try chan(1)? else "latest" end,
124124
platform.string().split("-"))?
@@ -136,7 +136,7 @@ actor Main is PonyupNotify
136136
be default(ponyup: Ponyup, command: Command val, ponyup_dir: FilePath) =>
137137
let platform = command.arg("platform").string()
138138
try
139-
Packages.from_fragments("", "", "", platform.split("-"))?
139+
Packages.platform_os()?
140140
else
141141
log(Err, "invalid platform identifier: " + platform)
142142
return

cmd/packages.pony

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,65 @@
1+
class val Binary
2+
let name: String
3+
let required: Bool
4+
5+
new val create(n: String, req: Bool = true) =>
6+
name = n
7+
required = req
8+
9+
trait val Application
10+
fun name(): String
11+
fun binaries(): Array[Binary] val
12+
13+
primitive CorralApplication is Application
14+
fun name(): String => "corral"
15+
fun binaries(): Array[Binary] val => [Binary("corral")]
16+
17+
primitive PonycApplication is Application
18+
fun name(): String => "ponyc"
19+
fun binaries(): Array[Binary] val => [
20+
Binary("ponyc")
21+
Binary("pony-lsp", false)
22+
]
23+
24+
primitive PonyupApplication is Application
25+
fun name(): String => "ponyup"
26+
fun binaries(): Array[Binary] val => [Binary("ponyup")]
27+
28+
primitive ChangelogToolApplication is Application
29+
fun name(): String => "changelog-tool"
30+
fun binaries(): Array[Binary] val => [Binary("changelog-tool")]
31+
32+
primitive StableApplication is Application
33+
fun name(): String => "stable"
34+
fun binaries(): Array[Binary] val => [Binary("stable")]
135

236
primitive Packages
3-
fun apply(): Array[String] box =>
37+
fun apply(): Array[Application] box =>
438
ifdef windows then
5-
["corral"; "ponyc"; "ponyup"]
39+
[CorralApplication; PonycApplication; PonyupApplication]
640
else
7-
["changelog-tool"; "corral"; "ponyc"; "ponyup"; "stable"]
41+
[
42+
CorralApplication
43+
PonycApplication
44+
PonyupApplication
45+
ChangelogToolApplication
46+
StableApplication
47+
]
848
end
949

50+
fun application_from_string(name: String): Application ? =>
51+
match name
52+
| "ponyc" => PonycApplication
53+
| "corral" => CorralApplication
54+
| "ponyup" => PonyupApplication
55+
| "changelog-tool" => ChangelogToolApplication
56+
| "stable" => StableApplication
57+
else
58+
error
59+
end
60+
1061
fun from_fragments(
11-
name: String,
62+
application: Application,
1263
channel: String,
1364
version: String,
1465
platform: Array[String] box)
@@ -46,12 +97,12 @@ primitive Packages
4697
if i == (platform'.size() - 1) then distro = field end
4798
end
4899
end
49-
if (name == "ponyc") and platform_requires_distro(os) then
100+
if (application.name() == "ponyc") and platform_requires_distro(os) then
50101
if distro is None then error end
51102
else
52103
distro = None
53104
end
54-
Package._create(name, channel, version, (cpu, os, distro))
105+
Package._create(application, channel, version, (cpu, os, distro))
55106

56107
fun from_string(str: String): Package ? =>
57108
let fragments = str.split("-")
@@ -60,8 +111,9 @@ primitive Packages
60111
fragments.delete(1)?
61112
fragments(0)? = "changelog-tool"
62113
end
114+
63115
from_fragments(
64-
fragments(0)?,
116+
application_from_string(fragments(0)?)?,
65117
fragments(1)?,
66118
fragments(2)?,
67119
(consume fragments).slice(3))?
@@ -84,7 +136,7 @@ primitive Packages
84136
end
85137

86138
class val Package is Comparable[Package box]
87-
let name: String
139+
let application: Application
88140
let channel: String
89141
let version: String
90142
let cpu: CPU
@@ -93,24 +145,27 @@ class val Package is Comparable[Package box]
93145
let selected: Bool
94146

95147
new val _create(
96-
name': String,
148+
application': Application,
97149
channel': String,
98150
version': String,
99151
platform': (CPU, OS, Distro),
100152
selected': Bool = false)
101153
=>
102-
name = name'
154+
application = application'
103155
channel = channel'
104156
version = version'
105157
(cpu, os, distro) = platform'
106158
selected = selected'
107159

160+
fun name(): String =>
161+
application.name()
162+
108163
fun update_version(version': String, selected': Bool = false): Package =>
109-
_create(name, channel, version', (cpu, os, distro), selected')
164+
_create(application, channel, version', (cpu, os, distro), selected')
110165

111166
fun platform(): String iso^ =>
112167
let str = "-".join([cpu; os].values())
113-
match (name == "ponyc", distro)
168+
match (application.name() == "ponyc", distro)
114169
| (true, let distro_name: String) => str.append("-" + distro_name)
115170
end
116171
str
@@ -122,7 +177,7 @@ class val Package is Comparable[Package box]
122177
string() <= other.string()
123178

124179
fun string(): String iso^ =>
125-
"-".join([name; channel; version; platform()].values())
180+
"-".join([application.name(); channel; version; platform()].values())
126181

127182
type CPU is ((AMD64 | ARM64) & _CPU)
128183
interface val _CPU is (Equatable[_OS] & Stringable)

cmd/ponyup.pony

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,6 @@ actor Ponyup
5858
return
5959
end
6060

61-
if not Packages().contains(pkg.name, {(a, b) => a == b }) then
62-
_notify.log(Err, "unknown package: " + pkg.name)
63-
return
64-
end
65-
6661
if _lockfile.contains(pkg) then
6762
_notify.log(Info, pkg.string() + " is up to date")
6863
return
@@ -175,15 +170,15 @@ actor Ponyup
175170
end
176171

177172
_notify.log(Info, " ".join(
178-
[ "selecting"; pkg; "as default for"; pkg.name
173+
[ "selecting"; pkg; "as default for"; pkg.name()
179174
].values()))
180175

181176
var pkg' =
182177
try
183178
var p = pkg
184179
if p.version == "latest" then
185180
var latest = ""
186-
for installed in local_packages(p.name).values() do
181+
for installed in local_packages(p.name()).values() do
187182
if (installed.channel == p.channel) and (installed.version > latest)
188183
then latest = installed.version
189184
end
@@ -211,48 +206,63 @@ actor Ponyup
211206
end
212207

213208
ifdef windows then
214-
let link_rel: String = Path.sep().join(["bin"; pkg'.name].values())
215-
+ ".exe"
216-
let bin_rel: String = Path.sep().join([pkg'.string(); link_rel].values())
209+
for binary in pkg'.application.binaries().values() do
210+
let link_rel: String = Path.sep().join(["bin"; binary.name].values())
211+
+ ".exe"
212+
let bin_rel: String = Path.sep().join([pkg'.string(); link_rel].values())
217213

218-
try
219-
let bin_path = _root.join(bin_rel)?
220-
_notify.log(Info, " bin: " + bin_path.path)
214+
try
215+
let bin_path = _root.join(bin_rel)?
216+
_notify.log(Info, " bin: " + bin_path.path)
221217

222-
let link_dir = _root.join("bin")?
223-
if not link_dir.exists() then link_dir.mkdir() end
218+
let link_dir = _root.join("bin")?
219+
if not link_dir.exists() then link_dir.mkdir() end
224220

225-
let link_path = link_dir.join(pkg'.name + ".bat")?
226-
_notify.log(Info, "link: " + link_path.path)
221+
let link_path = link_dir.join(binary.name + ".bat")?
222+
_notify.log(Info, "link: " + link_path.path)
227223

228-
if link_path.exists() then link_path.remove() end
229-
with file = File.create(link_path) do
230-
file.print("@echo off")
231-
file.print("\"" + bin_path.path + "\" %*")
224+
if link_path.exists() then link_path.remove() end
225+
// It is ok for optional binaries to not exist. If they don't then
226+
// we just skip them.
227+
if (not binary.required) and (not bin_path.exists()) then
228+
_notify.log(Info, "optional binary isn't in package. skipping.")
229+
continue
230+
end
231+
with file = File.create(link_path) do
232+
file.print("@echo off")
233+
file.print("\"" + bin_path.path + "\" %*")
234+
end
235+
else
236+
_notify.log(Err, "failed to create link batch file(s)")
232237
end
233-
else
234-
_notify.log(Err, "failed to create link batch file")
235238
end
236-
237239
else
238-
let link_rel: String = "/".join(["bin"; pkg'.name].values())
239-
let bin_rel: String = "/".join([pkg'.string(); link_rel].values())
240+
for binary in pkg'.application.binaries().values() do
241+
let link_rel: String = "/".join(["bin"; binary.name].values())
242+
let bin_rel: String = "/".join([pkg'.string(); link_rel].values())
240243

241-
try
242-
let bin_path = _root.join(bin_rel)?
243-
_notify.log(Info, " bin: " + bin_path.path)
244+
try
245+
let bin_path = _root.join(bin_rel)?
246+
_notify.log(Info, " bin: " + bin_path.path)
244247

245-
let link_dir = _root.join("bin")?
246-
if not link_dir.exists() then link_dir.mkdir() end
248+
let link_dir = _root.join("bin")?
249+
if not link_dir.exists() then link_dir.mkdir() end
247250

248-
let link_path = link_dir.join(pkg'.name)?
249-
_notify.log(Info, "link: " + link_path.path)
251+
let link_path = link_dir.join(binary.name)?
252+
_notify.log(Info, "link: " + link_path.path)
250253

251-
if link_path.exists() then link_path.remove() end
252-
if not bin_path.symlink(link_path) then error end
253-
else
254-
_notify.log(Err, "failed to create symbolic link")
255-
return
254+
if link_path.exists() then link_path.remove() end
255+
// It is ok for optional binaries to not exist. If they don't then
256+
// we just skip them.
257+
if (not binary.required) and (not bin_path.exists()) then
258+
_notify.log(Info, "optional binary isn't in package. skipping.")
259+
continue
260+
end
261+
if not bin_path.symlink(link_path) then error end
262+
else
263+
_notify.log(Err, "failed to create symbolic link(s)")
264+
return
265+
end
256266
end
257267
end
258268

@@ -432,17 +442,17 @@ class LockFile
432442
let pkg = Packages.from_string(fields(0)?)?
433443
let selected = try fields(1)? == "*" else false end
434444

435-
let entry = _entries.get_or_else(pkg.name, LockFileEntry)
445+
let entry = _entries.get_or_else(pkg.name(), LockFileEntry)
436446
if selected then
437447
entry.selection = entry.packages.size()
438448
end
439449
entry.packages.push(pkg)
440-
_entries(pkg.name) = entry
450+
_entries(pkg.name()) = entry
441451
end
442452

443453
fun contains(pkg: Package): Bool =>
444454
if pkg.version == "latest" then return false end
445-
let entry = _entries.get_or_else(pkg.name, LockFileEntry)
455+
let entry = _entries.get_or_else(pkg.name(), LockFileEntry)
446456
entry.packages.contains(pkg, {(a, b) => a.string() == b.string() })
447457

448458
fun selection(pkg_name: String): (Package | None) =>
@@ -452,12 +462,12 @@ class LockFile
452462
end
453463

454464
fun ref add_package(pkg: Package) =>
455-
let entry = _entries.get_or_else(pkg.name, LockFileEntry)
465+
let entry = _entries.get_or_else(pkg.name(), LockFileEntry)
456466
entry.packages.push(pkg)
457-
_entries(pkg.name) = entry
467+
_entries(pkg.name()) = entry
458468

459469
fun ref select(pkg: Package) ? =>
460-
let entry = _entries(pkg.name)?
470+
let entry = _entries(pkg.name())?
461471
entry.selection = entry.packages.find(
462472
pkg where predicate = {(a, b) => a.string() == b.string() })?
463473

0 commit comments

Comments
 (0)